191 lines
7.7 KiB
Python
191 lines
7.7 KiB
Python
"""
|
||
Этот модуль управляет сохранением и извлечением истории графов
|
||
запросов, используя SQLite базу данных для хранения данных.
|
||
"""
|
||
|
||
from typing import Dict, Any, List, Optional
|
||
|
||
import json
|
||
|
||
import sqlite3
|
||
|
||
|
||
class GraphHistoryManager:
|
||
"""
|
||
Менеджер для хранения истории графов запросов в SQLite.
|
||
"""
|
||
|
||
def __init__(self, db_path="graph_history.db"):
|
||
self.db_path = db_path
|
||
|
||
def _get_connection(self):
|
||
"""Получает соединение с базой данных."""
|
||
return sqlite3.connect(self.db_path)
|
||
|
||
def _create_table(self, conn):
|
||
"""Создает таблицу для хранения графов, если она не существует."""
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
CREATE TABLE IF NOT EXISTS graphs (
|
||
id TEXT PRIMARY KEY,
|
||
messages TEXT,
|
||
graph_nodes TEXT,
|
||
graph_edges TEXT,
|
||
current_node_id TEXT
|
||
)
|
||
""")
|
||
conn.commit()
|
||
|
||
def _get_max_graph_id(self, conn):
|
||
"""Получает максимальный ID графа из базы данных."""
|
||
cursor = conn.cursor()
|
||
cursor.execute(
|
||
"SELECT MAX(CAST(SUBSTR(id, 7) AS INTEGER)) FROM graphs WHERE id LIKE 'graph_%'"
|
||
)
|
||
result = cursor.fetchone()[0]
|
||
return result if result is not None else 0
|
||
|
||
def save_graph(self, graph_data: Any) -> str:
|
||
"""Сохраняет или обновляет данные графа в базе данных и возвращает ID."""
|
||
conn = self._get_connection()
|
||
cursor = conn.cursor()
|
||
try:
|
||
graph_id = graph_data.get("id")
|
||
if not graph_id:
|
||
max_graph_id = self._get_max_graph_id(conn)
|
||
next_graph_id = max_graph_id + 1
|
||
graph_id = f"graph_{next_graph_id}"
|
||
graph_data["id"] = graph_id
|
||
|
||
# Преобразуем структуры данных в JSON-строки для хранения в SQLite
|
||
messages_json = json.dumps(graph_data.get("messages", []))
|
||
graph_nodes_json = json.dumps(graph_data.get("graph_nodes", []))
|
||
graph_edges_json = json.dumps(graph_data.get("graph_edges", []))
|
||
current_node_id = graph_data.get("current_node_id", "")
|
||
|
||
# Проверяем, существует ли уже запись с таким ID
|
||
cursor.execute("SELECT id FROM graphs WHERE id = ?", (graph_id, ))
|
||
existing_record = cursor.fetchone()
|
||
|
||
if existing_record:
|
||
# Обновляем существующую запись
|
||
cursor.execute(
|
||
"""
|
||
UPDATE graphs SET
|
||
messages = ?,
|
||
graph_nodes = ?,
|
||
graph_edges = ?,
|
||
current_node_id = ?
|
||
WHERE id = ?
|
||
""", (messages_json, graph_nodes_json, graph_edges_json,
|
||
current_node_id, graph_id))
|
||
else:
|
||
# Вставляем новую запись
|
||
cursor.execute(
|
||
"""
|
||
INSERT INTO graphs (id, messages, graph_nodes, graph_edges, current_node_id)
|
||
VALUES (?, ?, ?, ?, ?)
|
||
""", (graph_id, messages_json, graph_nodes_json,
|
||
graph_edges_json, current_node_id))
|
||
|
||
conn.commit()
|
||
print(f"Граф сохранен/обновлен: {graph_id}")
|
||
return graph_id
|
||
finally:
|
||
conn.close()
|
||
|
||
def get_graph(self, graph_id: str) -> Optional[Any]:
|
||
"""Получает данные графа по ID из базы данных."""
|
||
conn = self._get_connection()
|
||
cursor = conn.cursor()
|
||
try:
|
||
cursor.execute(
|
||
"SELECT messages, graph_nodes, graph_edges, current_node_id FROM graphs WHERE id = ?",
|
||
(graph_id, ))
|
||
result = cursor.fetchone()
|
||
|
||
if result:
|
||
messages_json, graph_nodes_json, graph_edges_json, current_node_id = result
|
||
# Преобразуем JSON-строки обратно в структуры данных Python
|
||
messages = json.loads(messages_json)
|
||
graph_nodes = json.loads(graph_nodes_json)
|
||
graph_edges = json.loads(graph_edges_json)
|
||
|
||
return {
|
||
"id": graph_id,
|
||
"messages": messages,
|
||
"graph_nodes": graph_nodes,
|
||
"graph_edges": graph_edges,
|
||
"current_node_id": current_node_id
|
||
}
|
||
else:
|
||
return None
|
||
finally:
|
||
conn.close()
|
||
|
||
def get_all_graphs_summary(self) -> List[Dict[str, str]]:
|
||
"""Возвращает краткий список всех сохраненных графов."""
|
||
conn = self._get_connection()
|
||
cursor = conn.cursor()
|
||
try:
|
||
cursor.execute("SELECT id, messages FROM graphs")
|
||
graphs_data = cursor.fetchall()
|
||
return [{
|
||
"id":
|
||
gid,
|
||
"first_message":
|
||
json.loads(messages)[0].get("content", "No content")
|
||
if messages else "No content"
|
||
} for gid, messages in graphs_data]
|
||
finally:
|
||
conn.close()
|
||
|
||
def get_messages_from_root_to_node(
|
||
self, graph_data: Dict[str, Any],
|
||
target_node_id: str) -> List[Dict[str, str]]:
|
||
"""Получает список сообщений от корневого узла до указанного узла."""
|
||
nodes = graph_data.get("graph_nodes", [])
|
||
edges = graph_data.get("graph_edges", [])
|
||
messages = graph_data.get("messages", [])
|
||
|
||
# Создаем словарь для быстрого поиска родительских узлов
|
||
parent_map = {edge['target']: edge['source'] for edge in edges}
|
||
|
||
# Функция для рекурсивного подъема по дереву до корневого узла
|
||
def get_path_to_root(node_id: str) -> List[str]:
|
||
path = [node_id]
|
||
while node_id in parent_map:
|
||
node_id = parent_map[node_id]
|
||
path.append(node_id)
|
||
return path[::-1] # Инвертируем, чтобы получить путь от корня
|
||
|
||
# Получаем путь от корня до целевого узла
|
||
path_to_root = get_path_to_root(target_node_id)
|
||
|
||
# Собираем сообщения, соответствующие узлам в пути
|
||
messages_for_path = []
|
||
node_ids_in_path = set(path_to_root)
|
||
|
||
for message in messages:
|
||
if "node_id" in message and message["node_id"] in node_ids_in_path:
|
||
messages_for_path.append({
|
||
"role": message["role"],
|
||
"content": message["content"]
|
||
})
|
||
|
||
return messages_for_path
|
||
|
||
def delete_graph(self, graph_id: str) -> bool:
|
||
"""Удаляет граф из базы данных."""
|
||
conn = self._get_connection()
|
||
cursor = conn.cursor()
|
||
try:
|
||
cursor.execute("DELETE FROM graphs WHERE id = ?", (graph_id, ))
|
||
conn.commit()
|
||
return True
|
||
except Exception as e:
|
||
print(f"Ошибка при удалении графа: {e}")
|
||
return False
|
||
finally:
|
||
conn.close()
|