llm-agent-backend/app/graph_history_manager.py
dimitrievgs d27156d152 init
2025-09-12 00:26:19 +03:00

191 lines
7.7 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.

"""
Этот модуль управляет сохранением и извлечением истории графов
запросов, используя 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()