Compare commits

...
2 Commits
7 changed files with 384 additions and 534 deletions
+27 -58
View File
@@ -1,78 +1,47 @@
import sqlite3
import os import os
from datetime import datetime import sqlite3
DB_NAME = "context_memory.db" DB_NAME = "/home/puh/scud_orion_ai_v2/data/scud_orion_ai.db"
def init_db(): def init_db():
os.makedirs(os.path.dirname(DB_NAME), exist_ok=True)
conn = sqlite3.connect(DB_NAME) conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor() cursor = conn.cursor()
# 1. Таблица трекинга задач
cursor.execute(""" cursor.execute("""
CREATE TABLE IF NOT EXISTS tasks ( CREATE TABLE IF NOT EXISTS tasks (
task_id TEXT PRIMARY KEY, task_id TEXT PRIMARY KEY,
module TEXT NOT NULL, module TEXT,
title TEXT NOT NULL, title TEXT,
status TEXT NOT NULL, status TEXT DEFAULT 'BACKLOG',
priority TEXT NOT NULL, priority TEXT DEFAULT 'HIGH',
completed_at TEXT due_date TEXT,
) created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP
);
""") """)
# 2. Таблица архитектурных правил и контекста
cursor.execute(""" cursor.execute("""
CREATE TABLE IF NOT EXISTS architecture_memory ( CREATE TABLE IF NOT EXISTS chat_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT PRIMARY KEY,
category TEXT NOT NULL, history_json TEXT NOT NULL,
rule_text TEXT NOT NULL, updated_at TEXT DEFAULT CURRENT_TIMESTAMP
is_active INTEGER DEFAULT 1, );
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
""") """)
# Первичное заполнение реестра задач [TASK-01] .. [TASK-17] cursor.execute("""
tasks_data = [ CREATE TABLE IF NOT EXISTS system_prompts (
("TASK-01", "services/data_loader", "Исправление KeyError по ФИО в Excel экспорте 1С (переход на .iloc)", "COMPLETED", "HIGH", "2026-08-03"), id INTEGER PRIMARY KEY AUTOINCREMENT,
("TASK-02", "services/zup_extractor", "Перевод подключения 1С:ЗУП на продакшн MS SQL сервер ACCOUNT-01 + гибридный фоллбэк", "COMPLETED", "HIGH", "2026-08-04"), name TEXT UNIQUE NOT NULL,
("TASK-03", "services/scud_export", "Инцидент Равина В.Э.: Фиксация 100% приоритета сырых логов pLogData СКУД над расчетом УРВ Ориона", "COMPLETED", "HIGH", "2026-08-04"), prompt_text TEXT NOT NULL,
("TASK-04", "core/database", "Реализация схемы SQLite WAL, сохранение снапшотов и маппинг колонок (department/position)", "COMPLETED", "HIGH", "2026-08-05"), is_active INTEGER DEFAULT 1,
("TASK-05", "core/database", "Исправление бага дублирования номеров снапшотов (переход на вычисление MAX() по суффиксу)", "COMPLETED", "HIGH", "2026-08-05"), updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
("TASK-06", "scripts/fix_snapshots", "Создание и выполнение скрипта сквозной переиндексации снапшотов с префиксом Y", "COMPLETED", "HIGH", "2026-08-05"), );
("TASK-07", "scripts/db_cli", "Реализация CLI-инспектора БД и выравнивание номеров срезов для идеальной верстки в терминале", "COMPLETED", "HIGH", "2026-08-05"), """)
("TASK-08", "main.py / logic", "Двойная проверка исключений по department_scud и 1С, удержание 100% приоритета кадровых документов", "COMPLETED", "HIGH", "2026-08-05"),
("TASK-09", "services/text_reporter", "Лаконичный вывод неразмеченных сотрудников с короткими аббревиатурами отделов СКУД (РУК, ОИЗ, ПУ и др.)", "COMPLETED", "HIGH", "2026-08-05"),
("TASK-10", "services/scud_export", "Внутридневной контроль: Извлечение полных цепочек проходов pLogData (входы/выходы за смену)", "BACKLOG", "HIGH", None),
("TASK-11", "services/data_loader", "Внутридневной контроль: Расчет количества и суммарной длительности отлучек/перекуров сверх обеда", "BACKLOG", "HIGH", None),
("TASK-12", "services/text_reporter", "Внутридневной контроль: Вывод списка системных нарушителей внутреннего распорядка в Markdown-отчет", "BACKLOG", "MEDIUM", None),
("TASK-13", "automation", "Автоматизация сбора срезов по расписанию (Cron / Systemd Timers на 12:00, 17:00, 19:00)", "BACKLOG", "HIGH", None),
("TASK-14", "services/notifications", "Интеграция Telegram-бота для алертов Администратору при массовых сбоях турникетов (>5%)", "BACKLOG", "MEDIUM", None),
("TASK-15", "api / fastapi", "Создание REST API на FastAPI (/api/v1/snapshots, /api/v1/scud/logs, /api/v1/anomalies)", "BACKLOG", "HIGH", None),
("TASK-16", "frontend / chat", "Веб-интерфейс с ИИ-чатом на базе Ollama (Qwen 2.5 14B) с поддержкой Function Calling к REST API", "BACKLOG", "HIGH", None),
("TASK-17", "core/memory", "Создание модуля персистентной памяти проекта в SQLite и CLI-инструмента для фиксации архитектурных решений", "IN_PROGRESS", "HIGH", None),
]
cursor.executemany("""
INSERT OR REPLACE INTO tasks (task_id, module, title, status, priority, completed_at)
VALUES (?, ?, ?, ?, ?, ?)
""", tasks_data)
# Базовые правила архитектуры
rules_data = [
("logic_rule", "Сырые логи pLogData СКУД имееют 100% приоритет над расчетом УРВ Ориона."),
("logic_rule", "Официальные кадровые документы 1С (отпуска, командировки, больничные) имеют абсолютный приоритет над отсутствием в СКУД."),
("scud_override", "Запрещено переименовывать ФИО сотрудников при 100% совпадении строк между 1С и СКУД в AI-верификаторе."),
("formatting", "Температура указывается исключительно в градусах Цельсия, скорости в км/ч.")
]
cursor.executemany("""
INSERT INTO architecture_memory (category, rule_text)
VALUES (?, ?)
""", rules_data)
conn.commit() conn.commit()
conn.close() conn.close()
print("[✓] База данных контекста и трекинга задач успешно создана и заполнена!") print(f"[✓] Единая база данных SQLite ({DB_NAME}) успешно проверкой инициализирована!")
if __name__ == "__main__": if __name__ == "__main__":
init_db() init_db()
View File
+130
View File
@@ -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
+129
View File
@@ -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
+96
View File
@@ -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"]
}
}
}
]
-474
View File
@@ -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
+2 -2
View File
@@ -10,7 +10,7 @@ from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, HTMLResponse from fastapi.responses import FileResponse, HTMLResponse
from pydantic import BaseModel from pydantic import BaseModel
from llm_agent import process_chat_message from llm.agent import process_chat_message
# === ANCHOR: IMPORTS_END === # === ANCHOR: IMPORTS_END ===
@@ -21,7 +21,7 @@ app = FastAPI(
description="REST API локального контекста, памяти и трекинга задач для ИИ-аудитора" description="REST API локального контекста, памяти и трекинга задач для ИИ-аудитора"
) )
DB_NAME = "context_memory.db" DB_NAME = "/home/puh/scud_orion_ai_v2/data/scud_orion_ai.db"
SECURITY_TOKEN = os.getenv("API_BEARER_TOKEN", "scud_secret_token_2026") SECURITY_TOKEN = os.getenv("API_BEARER_TOKEN", "scud_secret_token_2026")
security = HTTPBearer() security = HTTPBearer()
# === ANCHOR: APP_INIT_END === # === ANCHOR: APP_INIT_END ===