diff --git a/app/graph_history_manager.py b/app/graph_history_manager.py
index d0ff3da..4b2c728 100644
--- a/app/graph_history_manager.py
+++ b/app/graph_history_manager.py
@@ -17,6 +17,7 @@ class GraphHistoryManager:
def __init__(self, db_path="graph_history.db"):
self.db_path = db_path
+ self._create_table(self._get_connection()) # Создаем таблицу при инициализации
def _get_connection(self):
"""Получает соединение с базой данных."""
@@ -28,7 +29,6 @@ class GraphHistoryManager:
cursor.execute("""
CREATE TABLE IF NOT EXISTS graphs (
id TEXT PRIMARY KEY,
- messages TEXT,
graph_nodes TEXT,
graph_edges TEXT,
current_node_id TEXT
@@ -45,8 +45,12 @@ class GraphHistoryManager:
result = cursor.fetchone()[0]
return result if result is not None else 0
+ # ---------------------------------------------------- public methods ----------------------------------------------------------------
def save_graph(self, graph_data: Any) -> str:
- """Сохраняет или обновляет данные графа в базе данных и возвращает ID."""
+ """
+ Сохраняет или обновляет данные графа в базе данных и возвращает ID.
+ Не сохраняет полную историю сообщений, только структуру графа.
+ """
conn = self._get_connection()
cursor = conn.cursor()
try:
@@ -58,7 +62,6 @@ class GraphHistoryManager:
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", "")
@@ -72,20 +75,19 @@ class GraphHistoryManager:
cursor.execute(
"""
UPDATE graphs SET
- messages = ?,
graph_nodes = ?,
graph_edges = ?,
current_node_id = ?
WHERE id = ?
- """, (messages_json, graph_nodes_json, graph_edges_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,
+ INSERT INTO graphs (id, graph_nodes, graph_edges, current_node_id)
+ VALUES (?, ?, ?, ?)
+ """, (graph_id, graph_nodes_json,
graph_edges_json, current_node_id))
conn.commit()
@@ -94,29 +96,51 @@ class GraphHistoryManager:
finally:
conn.close()
- def get_graph(self, graph_id: str) -> Optional[Any]:
- """Получает данные графа по ID из базы данных."""
+ def get_graph(self, graph_id: str, target_node_id: Optional[str] = None) -> Optional[Dict[str, Any]]:
+ """
+ Получает данные графа по ID из базы данных.
+ Динамически вычисляет 'messages' до 'target_node_id'.
+ Если target_node_id не указан, используется current_node_id из БД.
+ """
conn = self._get_connection()
cursor = conn.cursor()
try:
cursor.execute(
- "SELECT messages, graph_nodes, graph_edges, current_node_id FROM graphs WHERE id = ?",
+ "SELECT 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
+ graph_nodes_json, graph_edges_json, db_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)
+ resolved_current_node_id = target_node_id if target_node_id else db_current_node_id
+
+ # Если нет узлов, значит граф пуст или некорректен
+ if not graph_nodes:
+ return {
+ "id": graph_id,
+ "messages": [],
+ "graph_nodes": [],
+ "graph_edges": [],
+ "current_node_id": resolved_current_node_id
+ }
+
+ # Динамически собираем сообщения
+ messages = self.get_messages_from_root_to_node(
+ {"graph_nodes": graph_nodes, "graph_edges": graph_edges},
+ resolved_current_node_id
+ )
+
return {
"id": graph_id,
- "messages": messages,
+ "messages": messages, # Здесь будут вычисленные сообщения
"graph_nodes": graph_nodes,
"graph_edges": graph_edges,
- "current_node_id": current_node_id
+ "current_node_id": resolved_current_node_id
}
else:
return None
@@ -124,54 +148,80 @@ class GraphHistoryManager:
conn.close()
def get_all_graphs_summary(self) -> List[Dict[str, str]]:
- """Возвращает краткий список всех сохраненных графов."""
+ """
+ Возвращает краткий список всех сохраненных графов.
+ Первое сообщение извлекается путем построения пути к current_node_id
+ и взятия первого сообщения.
+ """
conn = self._get_connection()
cursor = conn.cursor()
try:
- cursor.execute("SELECT id, messages FROM graphs")
+ cursor.execute("SELECT id, graph_nodes, graph_edges, current_node_id 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]
+ summaries = []
+ for gid, nodes_json, edges_json, current_node_id in graphs_data:
+ graph_nodes = json.loads(nodes_json)
+ graph_edges = json.loads(edges_json)
+
+ # Получаем все сообщения до current_node_id
+ full_messages = self.get_messages_from_root_to_node(
+ {"graph_nodes": graph_nodes, "graph_edges": graph_edges},
+ current_node_id
+ )
+
+ first_message_content = "No content"
+ if full_messages:
+ # Ищем первое сообщение, которое является пользовательским
+ for msg in full_messages:
+ if msg.get("role") == "user":
+ first_message_content = msg.get("content", "No content")
+ break
+ if first_message_content == "No content": # Если не нашли пользовательского, берем первое ассистента
+ first_message_content = full_messages[0].get("content", "No content")
+
+ summaries.append({
+ "id": gid,
+ "first_message": first_message_content
+ })
+ return summaries
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", [])
+ """
+ Получает список сообщений от корневого узла до указанного узла.
+ Сообщения извлекаются из данных узлов, а не из отдельного поля.
+ """
+ all_nodes = graph_data.get("graph_nodes", [])
edges = graph_data.get("graph_edges", [])
- messages = graph_data.get("messages", [])
+ # Создаем словарь для быстрого поиска узлов по ID
+ node_map = {node['id']: node for node in all_nodes}
# Создаем словарь для быстрого поиска родительских узлов
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)
+ current = node_id
+ while current in parent_map:
+ current = parent_map[current]
+ path.append(current)
return path[::-1] # Инвертируем, чтобы получить путь от корня
# Получаем путь от корня до целевого узла
- path_to_root = get_path_to_root(target_node_id)
+ path_to_target = get_path_to_root(target_node_id)
- # Собираем сообщения, соответствующие узлам в пути
+
+ # Собираем сообщения, соответствующие узлам в пути.
+ # Сообщения теперь хранятся в 'data' каждого узла при создании.
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"]
- })
+ for node_id in path_to_target:
+ node = node_map.get(node_id)
+ if node and 'message' in node.get('data', {}): # Проверяем наличие поля 'message'
+ messages_for_path.append(node['data']['message'])
return messages_for_path
@@ -182,7 +232,7 @@ class GraphHistoryManager:
try:
cursor.execute("DELETE FROM graphs WHERE id = ?", (graph_id, ))
conn.commit()
- return True
+ return cursor.rowcount > 0 # Возвращает True, если была удалена хотя бы одна строка
except Exception as e:
print(f"Ошибка при удалении графа: {e}")
return False
diff --git a/app/llm_client.py b/app/llm_client.py
index 30cdc52..c3230df 100644
--- a/app/llm_client.py
+++ b/app/llm_client.py
@@ -5,7 +5,7 @@
import requests
from typing import Dict, Any, List
-from langchain_core.messages import HumanMessage, SystemMessage
+from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
# --- Конфигурация LLM ---
# Пустой словарь для локальных моделей (Ollama)
@@ -101,6 +101,11 @@ class CustomLLM:
"role": "system",
"content": msg.content
})
+ elif isinstance(msg, AIMessage): # Добавляем обработку AIMessage
+ openai_messages.append({
+ "role": "assistant",
+ "content": msg.content
+ })
else: # Предполагаем, что это другие типы сообщений Langchain или словари
openai_messages.append({
"role":
@@ -155,6 +160,11 @@ class CustomLLM:
"role": "system",
"content": msg.content
})
+ elif isinstance(msg, AIMessage): # Добавляем обработку AIMessage
+ mistral_messages.append({
+ "role": "assistant",
+ "content": msg.content
+ })
else: # Предполагаем, что это другие типы сообщений Langchain или словари
mistral_messages.append({
"role":
diff --git a/app/models.py b/app/models.py
index 71cb887..f3e2809 100644
--- a/app/models.py
+++ b/app/models.py
@@ -14,11 +14,6 @@ class AgentState(BaseModel):
command: Optional[str] = Field(None,
description="Распознанная слеш-команда.")
command_args: List[str] = Field([], description="Аргументы команды.")
- chat_history: List[Dict[str, str]] = Field(
- [],
- description=
- "Полная история сообщений в текущей ветке диалога. Формат: [{'role': 'user/assistant', 'content': 'message'}]"
- )
llm_response: Optional[str] = Field(None, description="Ответ от LLM.")
image_urls: List[str] = Field(
[], description="URL сгенерированных изображений.")
@@ -37,3 +32,7 @@ class AgentState(BaseModel):
"start", description="ID текущего узла графа для фронтенда.")
parent_node_id: Optional[str] = Field(None,
description="ID родительского узла.")
+ # Временное хранилище для chat_history, которое не сохраняется в БД
+ # Оно используется для передачи контекста между узлами в пределах одного выполнения
+ temporary_chat_history: List[Dict[str, Any]] = Field(
+ [], description="Временная история чата, не сохраняемая в БД.")
diff --git a/app/nodes.py b/app/nodes.py
index 5948292..3f694e4 100644
--- a/app/nodes.py
+++ b/app/nodes.py
@@ -4,7 +4,7 @@
разбор команд, вызов LLM или выполнение сервисов.
"""
-from langchain_core.messages import HumanMessage, SystemMessage
+from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from llm_client import get_llm
from services import ImageGenerationService, SubtitleService, ImageAnalysisService, SummarizationService
@@ -66,21 +66,28 @@ class CommandManager:
command_manager = CommandManager()
+# Здесь мы меняем состояние графа, добавляя сообщение от пользователя
def parse_command_node(state: AgentState) -> AgentState:
- """Парсит вход пользователя на предмет слеш-команды."""
+ """
+ Парсит вход пользователя на предмет слеш-команды и инициализирует узел пользователя в графе.
+ """
print("Выполняется узел: parse_command_node")
user_input = state.input.strip()
- # Добавляем узел пользователя в граф для фронтенда
+ # Создаем ID для узла пользователя
user_node_id = f"user_{len(state.graph_nodes) + 1}"
+
+ # Добавляем узел пользователя в граф для фронтенда с полным текстом сообщения
state.graph_nodes.append({
"id": user_node_id,
"type": "user",
"data": {
- "label":
- user_input[:30] + "..." if len(user_input) > 30 else user_input
+ "label": user_input[:30] + "..." if len(user_input) > 30 else user_input,
+ "message": {"role": "user", "content": user_input, "node_id": user_node_id} # Сохраняем сообщение здесь
}
})
+
+ # Добавляем ребро к новому узлу, если есть родительский узел
if state.parent_node_id:
state.graph_edges.append({
"id": f"e{state.parent_node_id}-{user_node_id}",
@@ -90,10 +97,12 @@ def parse_command_node(state: AgentState) -> AgentState:
state.parent_node_id = user_node_id
state.current_node_id = user_node_id
- state.chat_history.append({
+ # Обновляем временную историю чата, которая используется для передачи между узлами
+ # и для вызова LLM в текущем раунде.
+ state.temporary_chat_history.append({
"role": "user",
"content": user_input,
- "node_id": user_node_id # Сохраняем ID узла
+ "node_id": user_node_id
})
if user_input.startswith('/'):
@@ -116,7 +125,10 @@ def parse_command_node(state: AgentState) -> AgentState:
def execute_command_node(state: AgentState) -> AgentState:
- """Выполняет соответствующий workflow для команды."""
+ """
+ Выполняет соответствующий workflow для команды.
+ Этот узел служит маршрутизатором для выбора следующего узла в зависимости от команды.
+ """
print(
f"Выполняется узел: execute_command_node для команды: {state.command}")
# Этот узел будет выступать в роли маршрутизатора
@@ -131,27 +143,32 @@ main_llm = get_llm(DEFAULT_LLM_NAME)
def call_llm_node(state: AgentState) -> AgentState:
- """Вызывает LLM для обработки обычного диалога или генерации ответа."""
+ """
+ Вызывает LLM для обработки обычного диалога или генерации ответа.
+ Использует `temporary_chat_history` для передачи контекста LLM.
+ """
print("Выполняется узел: call_llm_node (для обычного чата)")
try:
- # Для обычного чата LLM использует всю историю
+ # TODO: добавить в переписку ещё SystemMessage с системным промптом, типа: messages_for_llm.append(SystemMessage(content=msg["content"]))
+ # Для обычного чата LLM использует текущую временную историю
messages_for_llm = []
- for msg in state.chat_history:
+ for msg in state.temporary_chat_history: # Используем temporary_chat_history
if msg["role"] == "user":
messages_for_llm.append(HumanMessage(content=msg["content"]))
elif msg["role"] == "assistant":
- messages_for_llm.append(SystemMessage(content=msg["content"]))
+ messages_for_llm.append(AIMessage(content=msg["content"]))
response = main_llm.invoke(messages_for_llm)
- # Добавляем узел LLM в граф для фронтенда
llm_node_id = f"llm_{len(state.graph_nodes) + 1}"
+
+ # Добавляем узел LLM в граф для фронтенда с полным текстом ответа
state.graph_nodes.append({
"id": llm_node_id,
"type": "llm",
"data": {
- "label":
- response[:30] + "..." if len(response) > 30 else response
+ "label": response[:30] + "..." if len(response) > 30 else response,
+ "message": {"role": "assistant", "content": response, "node_id": llm_node_id} # Сохраняем сообщение здесь
}
})
state.graph_edges.append({
@@ -162,10 +179,11 @@ def call_llm_node(state: AgentState) -> AgentState:
state.parent_node_id = llm_node_id
state.current_node_id = llm_node_id
- state.chat_history.append({
+ # Обновляем временную историю чата с ответом LLM
+ state.temporary_chat_history.append({
"role": "assistant",
"content": response,
- "node_id": llm_node_id # Сохраняем ID узла
+ "node_id": llm_node_id
})
state.llm_response = response
@@ -182,12 +200,16 @@ image_gen_service = ImageGenerationService(
def generate_images_node(state: AgentState) -> AgentState:
- """Генерирует изображения по промпту."""
+ """
+ Генерирует изображения по промпту, переданному в команде `/imagine`.
+ """
print("Выполняется узел: generate_images_node")
prompt = " ".join(state.command_args)
if not prompt:
state.error = "Для команды /imagine требуется промпт. Пример: /imagine кошка на луне"
state.llm_response = state.error
+ # Добавляем ошибку во временную историю для согласованности
+ state.temporary_chat_history.append({"role": "assistant", "content": state.error})
return state
try:
@@ -196,20 +218,21 @@ def generate_images_node(state: AgentState) -> AgentState:
# Формируем текст ответа с использованием Markdown для отображения изображений
image_markdown = "\n".join([
- f'
'
+ f'
'
for i, url in enumerate(urls)
])
response_text = f"Сгенерировано {len(urls)} изображений:\n{image_markdown}"
state.llm_response = response_text
- # Добавляем узел генерации изображений в граф
img_gen_node_id = f"img_gen_{len(state.graph_nodes) + 1}"
+ # Добавляем узел генерации изображений в граф
state.graph_nodes.append({
"id": img_gen_node_id,
"type": "tool",
"data": {
"label": "Генерация изображений",
- "details": prompt
+ "details": prompt,
+ "message": {"role": "assistant", "content": response_text, "node_id": img_gen_node_id}
}
})
state.graph_edges.append({
@@ -220,8 +243,8 @@ def generate_images_node(state: AgentState) -> AgentState:
state.parent_node_id = img_gen_node_id
state.current_node_id = img_gen_node_id
- state.llm_response = "Сгенерированы изображения."
- state.chat_history.append({
+ # Обновляем временную историю чата
+ state.temporary_chat_history.append({
"role": "assistant",
"content": response_text,
"node_id": img_gen_node_id # Привязываем к ID узла
@@ -231,6 +254,8 @@ def generate_images_node(state: AgentState) -> AgentState:
state.error = f"Ошибка генерации изображений: {e}"
state.llm_response = f"Произошла ошибка при генерации изображений: {e}"
print(state.error)
+ # Добавляем ошибку во временную историю
+ state.temporary_chat_history.append({"role": "assistant", "content": state.llm_response})
return state
@@ -239,11 +264,15 @@ image_analysis_service = ImageAnalysisService(
def analyze_image_node(state: AgentState) -> AgentState:
- """Анализирует изображение по URL или данным."""
+ """
+ Анализирует изображение по URL и промпту, переданным в команде `/analyze`.
+ """
print("Выполняется узел: analyze_image_node")
if len(state.command_args) < 2:
state.error = "Для команды /analyze требуется URL изображения и промпт. Пример: /analyze [url] 'что на изображении?'"
state.llm_response = state.error
+ # Добавляем ошибку во временную историю для согласованности
+ state.temporary_chat_history.append({"role": "assistant", "content": state.error})
return state
image_url = state.command_args[0]
@@ -253,20 +282,18 @@ def analyze_image_node(state: AgentState) -> AgentState:
analysis_result = image_analysis_service.analyze_image(
image_url, prompt)
state.analysis_result = analysis_result
- state.llm_response = f"Результат анализа изображения '{image_url}': {analysis_result}"
- state.chat_history.append({
- "role": "assistant",
- "content": state.llm_response
- })
+ response_text = f"Результат анализа изображения '{image_url}': {analysis_result}"
+ state.llm_response = response_text
- # Добавляем узел анализа изображения в граф
img_analysis_node_id = f"img_analysis_{len(state.graph_nodes) + 1}"
+ # Добавляем узел анализа изображения в граф
state.graph_nodes.append({
"id": img_analysis_node_id,
"type": "tool",
"data": {
"label": "Анализ изображения",
- "details": f"URL: {image_url}, Промпт: {prompt}"
+ "details": f"URL: {image_url}, Промпт: {prompt}",
+ "message": {"role": "assistant", "content": response_text, "node_id": img_analysis_node_id}
}
})
state.graph_edges.append({
@@ -277,10 +304,19 @@ def analyze_image_node(state: AgentState) -> AgentState:
state.parent_node_id = img_analysis_node_id
state.current_node_id = img_analysis_node_id
+ # Обновляем временную историю чата
+ state.temporary_chat_history.append({
+ "role": "assistant",
+ "content": response_text,
+ "node_id": img_analysis_node_id
+ })
+
except Exception as e:
state.error = f"Ошибка анализа изображения: {e}"
state.llm_response = f"Произошла ошибка при анализе изображения: {e}"
print(state.error)
+ # Добавляем ошибку во временную историю
+ state.temporary_chat_history.append({"role": "assistant", "content": state.llm_response})
return state
@@ -288,25 +324,25 @@ subtitle_service = SubtitleService()
def get_meet_subtitles_node(state: AgentState) -> AgentState:
- """Получает субтитры из Google Meet."""
+ """
+ Получает субтитры из Google Meet через команду `/subtitles_meet`.
+ """
print("Выполняется узел: get_meet_subtitles_node")
try:
subtitles = subtitle_service.get_google_meet_subtitles()
state.subtitles = subtitles
- state.llm_response = f"Текущие субтитры из Google Meet: {subtitles}"
- state.chat_history.append({
- "role": "assistant",
- "content": state.llm_response
- })
+ response_text = f"Текущие субтитры из Google Meet: {subtitles}"
+ state.llm_response = response_text
- # Добавляем узел субтитров Meet в граф
meet_subtitles_node_id = f"meet_subtitles_{len(state.graph_nodes) + 1}"
+ # Добавляем узел субтитров Meet в граф
state.graph_nodes.append({
"id": meet_subtitles_node_id,
"type": "tool",
"data": {
"label": "Субтитры Google Meet",
- "details": subtitles[:30] + "..." if subtitles else ""
+ "details": subtitles[:30] + "..." if subtitles else "",
+ "message": {"role": "assistant", "content": response_text, "node_id": meet_subtitles_node_id}
}
})
state.graph_edges.append({
@@ -317,33 +353,42 @@ def get_meet_subtitles_node(state: AgentState) -> AgentState:
state.parent_node_id = meet_subtitles_node_id
state.current_node_id = meet_subtitles_node_id
+ # Обновляем временную историю чата
+ state.temporary_chat_history.append({
+ "role": "assistant",
+ "content": response_text,
+ "node_id": meet_subtitles_node_id
+ })
+
except Exception as e:
state.error = f"Ошибка получения субтитров Google Meet: {e}"
state.llm_response = f"Произошла ошибка при получении субтитров Google Meet: {e}"
print(state.error)
+ # Добавляем ошибку во временную историю
+ state.temporary_chat_history.append({"role": "assistant", "content": state.llm_response})
return state
def get_teams_subtitles_node(state: AgentState) -> AgentState:
- """Получает субтитры из MS Teams."""
+ """
+ Получает субтитры из MS Teams через команду `/subtitles_teams`.
+ """
print("Выполняется узел: get_teams_subtitles_node")
try:
subtitles = subtitle_service.get_ms_teams_subtitles()
state.subtitles = subtitles
- state.llm_response = f"Текущие субтитры из MS Teams: {subtitles}"
- state.chat_history.append({
- "role": "assistant",
- "content": state.llm_response
- })
+ response_text = f"Текущие субтитры из MS Teams: {subtitles}"
+ state.llm_response = response_text
- # Добавляем узел субтитров Teams в граф
teams_subtitles_node_id = f"teams_subtitles_{len(state.graph_nodes) + 1}"
+ # Добавляем узел субтитров Teams в граф
state.graph_nodes.append({
"id": teams_subtitles_node_id,
"type": "tool",
"data": {
"label": "Субтитры MS Teams",
- "details": subtitles[:30] + "..." if subtitles else ""
+ "details": subtitles[:30] + "..." if subtitles else "",
+ "message": {"role": "assistant", "content": response_text, "node_id": teams_subtitles_node_id}
}
})
state.graph_edges.append({
@@ -354,10 +399,19 @@ def get_teams_subtitles_node(state: AgentState) -> AgentState:
state.parent_node_id = teams_subtitles_node_id
state.current_node_id = teams_subtitles_node_id
+ # Обновляем временную историю чата
+ state.temporary_chat_history.append({
+ "role": "assistant",
+ "content": response_text,
+ "node_id": teams_subtitles_node_id
+ })
+
except Exception as e:
state.error = f"Ошибка получения субтитров MS Teams: {e}"
state.llm_response = f"Произошла ошибка при получении субтитров MS Teams: {e}"
print(state.error)
+ # Добавляем ошибку во временную историю
+ state.temporary_chat_history.append({"role": "assistant", "content": state.llm_response})
return state
@@ -366,28 +420,27 @@ summarization_service = SummarizationService(
def summarize_history_node(state: AgentState) -> AgentState:
- """Суммирует историю диалога."""
+ """
+ Суммирует историю диалога, используя `temporary_chat_history` в качестве контекста.
+ """
print("Выполняется узел: summarize_history_node")
try:
- # Для суммаризации используем полную историю, которую ведет AgentState
- summarized_text = summarization_service.summarize_history(
- state.chat_history)
+ # Для суммаризации используем временную историю, подготовленную в предыдущих узлах
+ summarized_text = summarization_service.summarize_history_of_dicts(
+ state.temporary_chat_history) # Изменен метод для использования List[Dict]
state.summarized_history_text = summarized_text
- state.llm_response = f"История диалога суммирована:\n{summarized_text}"
- state.chat_history.append({
- "role": "assistant",
- "content": state.llm_response
- })
+ response_text = f"История диалога суммирована:\n{summarized_text}"
+ state.llm_response = response_text
- # Добавляем узел суммаризации в граф
summary_node_id = f"summary_{len(state.graph_nodes) + 1}"
+ # Добавляем узел суммаризации в граф
state.graph_nodes.append({
"id": summary_node_id,
"type": "tool",
"data": {
"label": "Суммаризация истории",
- "details":
- summarized_text[:30] + "..." if summarized_text else ""
+ "details": summarized_text[:30] + "..." if summarized_text else "",
+ "message": {"role": "assistant", "content": response_text, "node_id": summary_node_id}
}
})
state.graph_edges.append({
@@ -398,30 +451,38 @@ def summarize_history_node(state: AgentState) -> AgentState:
state.parent_node_id = summary_node_id
state.current_node_id = summary_node_id
+ # Обновляем временную историю чата
+ state.temporary_chat_history.append({
+ "role": "assistant",
+ "content": response_text,
+ "node_id": summary_node_id
+ })
+
except Exception as e:
state.error = f"Ошибка суммаризации истории: {e}"
state.llm_response = f"Произошла ошибка при суммаризации истории: {e}"
print(state.error)
+ # Добавляем ошибку во временную историю
+ state.temporary_chat_history.append({"role": "assistant", "content": state.llm_response})
return state
def handle_error_node(state: AgentState) -> AgentState:
- """Обрабатывает ошибки команд."""
+ """
+ Обрабатывает ошибки команд, формируя ответ для пользователя и добавляя его в граф.
+ """
print(f"Выполняется узел: handle_error_node. Ошибка: {state.error}")
state.llm_response = state.error
- state.chat_history.append({
- "role": "assistant",
- "content": state.llm_response
- })
- # Добавляем узел ошибки в граф
error_node_id = f"error_{len(state.graph_nodes) + 1}"
+ # Добавляем узел ошибки в граф
state.graph_nodes.append({
"id": error_node_id,
"type": "error",
"data": {
"label": "Ошибка",
- "details": state.error
+ "details": state.error,
+ "message": {"role": "assistant", "content": state.llm_response, "node_id": error_node_id}
}
})
state.graph_edges.append({
@@ -432,30 +493,36 @@ def handle_error_node(state: AgentState) -> AgentState:
state.parent_node_id = error_node_id
state.current_node_id = error_node_id
+ # Обновляем временную историю чата
+ state.temporary_chat_history.append({
+ "role": "assistant",
+ "content": state.llm_response,
+ "node_id": error_node_id
+ })
+
return state
def help_node(state: AgentState) -> AgentState:
- """Предоставляет информацию о доступных командах."""
+ """
+ Предоставляет информацию о доступных командах, формируя ответ для пользователя и добавляя его в граф.
+ """
print("Выполняется узел: help_node")
commands_info = command_manager.get_commands()
help_text = "Доступные команды:\n"
for cmd, info in commands_info.items():
help_text += f"/{cmd}: {info['description']}\n"
state.llm_response = help_text
- state.chat_history.append({
- "role": "assistant",
- "content": state.llm_response
- })
- # Добавляем узел справки в граф
help_node_id = f"help_{len(state.graph_nodes) + 1}"
+ # Добавляем узел справки в граф
state.graph_nodes.append({
"id": help_node_id,
"type": "info",
"data": {
"label": "Справка",
- "details": "Список команд"
+ "details": "Список команд",
+ "message": {"role": "assistant", "content": state.llm_response, "node_id": help_node_id}
}
})
state.graph_edges.append({
@@ -466,4 +533,11 @@ def help_node(state: AgentState) -> AgentState:
state.parent_node_id = help_node_id
state.current_node_id = help_node_id
+ # Обновляем временную историю чата
+ state.temporary_chat_history.append({
+ "role": "assistant",
+ "content": state.llm_response,
+ "node_id": help_node_id
+ })
+
return state
diff --git a/app/workflows.py b/app/workflows.py
index ff214ca..46e5339 100644
--- a/app/workflows.py
+++ b/app/workflows.py
@@ -97,41 +97,57 @@ def run_agent(user_input: str,
Может продолжить существующий граф по graph_id.
Возвращает обновленное состояние и ID графа.
"""
+
+ # ---------------------------------------------------- Initial State Setup ----------------------------------------------------------------
initial_state = AgentState(input=user_input, parent_node_id=parent_node_id)
- # Если есть существующий ID графа, загружаем его историю
+ # Если есть существующий ID графа, загружаем его историю и структуру
+ loaded_graph_data = None
if existing_graph_id:
- existing_graph_data = graph_history_manager.get_graph(
- existing_graph_id)
- if existing_graph_data:
+ # Получаем данные графа, включая сгенерированные сообщения до parent_node_id (либо current_node_id из БД)
+ # Если parent_node_id передан, используем его для построения истории.
+ loaded_graph_data = graph_history_manager.get_graph(
+ existing_graph_id, target_node_id=parent_node_id)
+
+ if loaded_graph_data:
print(f"Продолжаю существующий граф {existing_graph_id}")
- initial_state.chat_history = existing_graph_data.get(
- "messages", [])
- initial_state.graph_nodes = existing_graph_data.get(
- "graph_nodes", [])
- initial_state.graph_edges = existing_graph_data.get(
- "graph_edges", [])
- initial_state.parent_node_id = parent_node_id or existing_graph_data.get(
- "current_node_id")
+ # Загружаем узлы и ребра для продолжения графа
+ initial_state.graph_nodes = loaded_graph_data.get("graph_nodes", [])
+ initial_state.graph_edges = loaded_graph_data.get("graph_edges", [])
+
+ # Устанавливаем parent_node_id для нового входного узла
+ # Если parent_node_id был передан в запросе, используем его,
+ # иначе берем current_node_id из загруженного графа.
+ initial_state.parent_node_id = parent_node_id or loaded_graph_data.get("current_node_id")
+
+ # История чата для текущего раунда работы LLM формируется из загруженных сообщений.
+ initial_state.temporary_chat_history = loaded_graph_data.get("messages", [])
+
+ # ---------------------------------------------------- run_agent ----------------------------------------------------------------
# Запускаем граф
result = app.invoke(initial_state)
final_state = AgentState(**result)
- # Сохраняем обновленное состояние графа
+ # ---------------------------------------------------- Save Graph ----------------------------------------------------------------
+ # Сохраняем обновленное состояние графа.
+ # Обратите внимание: chat_history больше не сохраняется напрямую,
+ # она является частью данных узлов в graph_nodes.
graph_data_to_save = {
- "id": existing_graph_id,
- "messages": final_state.chat_history,
+ "id": existing_graph_id, # Используем существующий ID или он будет сгенерирован
+ # менеджером истории при первом сохранении
"graph_nodes": final_state.graph_nodes,
"graph_edges": final_state.graph_edges,
"current_node_id": final_state.current_node_id
}
new_graph_id = graph_history_manager.save_graph(graph_data_to_save)
- # Формируем ответ для фронтенда
+ # ---------------------------------------------------- prepare response ----------------------------------------------------------------
+ # Для `messages` в `response_data` используем `final_state.temporary_chat_history`,
+ # так как она отражает только сообщения текущей ветки, добавленные в ходе этого выполнения.
response_data = {
"graph_id": new_graph_id,
- "messages": final_state.chat_history,
+ "messages": final_state.temporary_chat_history,
"llm_response": final_state.llm_response,
"image_urls": final_state.image_urls,
"analysis_result": final_state.analysis_result,
diff --git a/graph_history.db b/graph_history.db
index bfc11bc..e2c7b1c 100644
Binary files a/graph_history.db and b/graph_history.db differ