Files
scud_orion_ai_v2/ref.py
T

525 lines
22 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
import os
# Базовый путь к проекту
BASE_DIR = "/home/puh/scud_orion_ai_v2"
# 1. Данные для файла app/db/database.py
DB_PY = '''import sqlite3
DB_NAME = "/home/puh/scud_orion_ai_v2/data/scud_orion_ai.db"
def get_connection():
return sqlite3.connect(DB_NAME)
def db_get_active_system_prompt() -> str:
"""Извлекает активный системный промпт из базы данных SQLite."""
try:
conn = get_connection()
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 = get_connection()
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_rules() -> list:
"""Извлекает правила из таблицы ai_knowledge_base."""
conn = get_connection()
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
def db_add_rule(rule_text: str, added_by: str = "Operator") -> dict:
"""Добавляет новое правило или инструкцию в таблицу ai_knowledge_base."""
clean_text = rule_text.replace("**", "")
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"INSERT INTO ai_knowledge_base (rule_text, added_by) VALUES (?, ?)",
(clean_text, added_by)
)
rule_id = cursor.lastrowid
conn.commit()
conn.close()
return {"status": "success", "message": f"Правило #{rule_id} успешно добавлено в базу знаний!", "rule_id": rule_id}
def db_get_tasks(status: str = None) -> list:
"""Извлекает список задач из реестра БД."""
conn = get_connection()
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 = get_connection()
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 = get_connection()
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 = get_connection()
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
'''
# 2. Данные для файла app/tools/definitions.py
TOOLS_DEFINITIONS_PY = '''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_add_rule",
"description": "Добавить новое правило, инструкцию или факт в базу знаний (ai_knowledge_base). Вызывай при командах 'добавь в базу знаний', 'запомни правило', 'запиши инструкцию'.",
"parameters": {
"type": "object",
"properties": {
"rule_text": {"type": "string", "description": "Текст правила, инструкции или факта для сохранения"},
"added_by": {"type": "string", "description": "Автор или источник правила, по умолчанию Operator"}
},
"required": ["rule_text"]
}
}
},
{
"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"]
}
}
}
]
'''
# 3. Данные для файла app/tools/handlers.py
TOOLS_HANDLERS_PY = '''from app.db import database
def handle_tool_call(fn_name: str, fn_args: dict) -> str:
"""Диспетчеризация вызова функций и форматирование текстового ответа."""
if fn_name == "db_get_tasks":
tasks = database.db_get_tasks(status=fn_args.get("status"))
if not tasks:
return "Список задач пуст."
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"
return formatted_text
elif fn_name == "db_get_rules":
rules = database.db_get_rules()
if not rules:
return "База знаний пуста."
formatted_text = "🧠 База знаний ИИ (ai_knowledge_base):\n\n"
for r in rules:
formatted_text += f"{r.get('id')}. {r.get('rule_text')}\n\n"
return formatted_text
elif fn_name == "db_add_rule":
res = database.db_add_rule(
rule_text=fn_args.get("rule_text", ""),
added_by=fn_args.get("added_by", "Operator")
)
return res.get("message", "Правило добавлено в базу знаний.")
elif fn_name == "db_get_system_prompt":
prompt_text = database.db_get_active_system_prompt()
return f"⚙️ Актуальный системный промпт ИИ (из базы SQLite):\n\n{prompt_text}"
elif fn_name == "db_add_system_prompt":
res = database.db_add_system_prompt(
name=fn_args.get("name", "main_agent"),
prompt_text=fn_args.get("prompt_text", "")
)
return res.get("message", "Промпт обновлен.")
elif fn_name == "db_add_task":
res = database.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:
return f"Ошибка добавления задачи: {res['error']}"
assigned_id = res.get("task_id", "TASK")
due_str = fn_args.get('due_date') or 'Без срока'
return (
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')}"
)
elif fn_name == "db_update_task_status":
res = database.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:
return f"Ошибка обновления задачи: {res['error']}"
return f"Статус задачи {fn_args.get('task_id')} успешно обновлен!"
elif fn_name == "db_delete_task":
res = database.db_delete_task(task_id=fn_args.get("task_id"))
if "error" in res:
return f"Ошибка удаления задачи: {res['error']}"
return f"Задача {fn_args.get('task_id')} успешно удалена из базы!"
return f"Функция {fn_name} выполнена."
'''
# 4. Данные для файла app/core/ollama_client.py
OLLAMA_CLIENT_PY = '''import json
import urllib.request
import urllib.error
OLLAMA_URL = "http://192.168.11.3:11434/api/chat"
MODEL_NAME = "qwen2.5:14b"
def send_ollama_request(messages: list, tools: list) -> dict:
"""Отправляет HTTP-запрос к локальной модели Ollama."""
payload = {
"model": MODEL_NAME,
"messages": messages,
"tools": tools,
"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:
return json.loads(response.read().decode("utf-8"))
except urllib.error.URLError as e:
raise ConnectionError(f"Ошибка связи с Ollama ({OLLAMA_URL}): {e}")
'''
# 5. Данные для файла app/orchestrator.py
ORCHESTRATOR_PY = '''import json
from typing import List, Dict, Any, Tuple
from datetime import datetime
from app.db import database
from app.tools.definitions import TOOLS_SCHEMA
from app.tools.handlers import handle_tool_call
from app.core.ollama_client import send_ollama_request
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 = database.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 = database.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
# Загрузка актуального промпта
dynamic_prompt_text = database.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}]
try:
res_data = send_ollama_request(messages=messages, tools=TOOLS_SCHEMA)
msg = res_data.get("message", {})
tool_calls = msg.get("tool_calls", [])
content_str = msg.get("content", "").strip().replace("**", "")
# Перехват текстового JSON с именем функции
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", {})
formatted_text = handle_tool_call(fn_name, fn_args)
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 Exception as e:
return f"Ошибка обработки запроса: {e}", chat_history
'''
# 6. Обновленный главный модуль llm_agent.py
LLM_AGENT_PY = '''"""
SCUD Orion AI — Главная точка входа для общения с LLM-ассистентом.
Перенаправляет вызовы в центральный оркестратор app.orchestrator.
"""
from app.orchestrator import process_chat_message
__all__ = ["process_chat_message"]
'''
def build_structure():
files = {
os.path.join(BASE_DIR, "app", "__init__.py"): "",
os.path.join(BASE_DIR, "app", "db", "__init__.py"): "",
os.path.join(BASE_DIR, "app", "db", "database.py"): DB_PY,
os.path.join(BASE_DIR, "app", "tools", "__init__.py"): "",
os.path.join(BASE_DIR, "app", "tools", "definitions.py"): TOOLS_DEFINITIONS_PY,
os.path.join(BASE_DIR, "app", "tools", "handlers.py"): TOOLS_HANDLERS_PY,
os.path.join(BASE_DIR, "app", "core", "__init__.py"): "",
os.path.join(BASE_DIR, "app", "core", "ollama_client.py"): OLLAMA_CLIENT_PY,
os.path.join(BASE_DIR, "app", "orchestrator.py"): ORCHESTRATOR_PY,
os.path.join(BASE_DIR, "llm_agent.py"): LLM_AGENT_PY,
}
print("🚀 Начинаем рефакторинг проекта...")
for filepath, content in files.items():
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, "w", encoding="utf-8") as f:
f.write(content.strip() + "\n")
print(f" [✓] Создан/обновлен: {filepath}")
print("\n✅ Рефакторинг завершен успешно!")
if __name__ == "__main__":
build_structure()