work on separation / streaming
This commit is contained in:
parent
ee84617a1c
commit
45d892127d
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
|
@ -1,3 +1,5 @@
|
|||
{
|
||||
"terminal.integrated.defaultProfile.windows": "Git Bash"
|
||||
"terminal.integrated.defaultProfile.windows": "Git Bash",
|
||||
"python-envs.defaultEnvManager": "ms-python.python:venv",
|
||||
"python-envs.pythonProjects": []
|
||||
}
|
||||
|
|
|
|||
30
app/api.py
30
app/api.py
|
|
@ -7,6 +7,7 @@
|
|||
import json
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
from flask_socketio import SocketIO, emit
|
||||
from workflows import run_agent, graph_history_manager, run_agent_streaming
|
||||
from llm_client import MODELS # Добавляем импорт списка моделей
|
||||
|
||||
|
|
@ -16,6 +17,26 @@ CORS(
|
|||
) # Разрешаем CORS для всех доменов (в production нужно настроить более строго)
|
||||
|
||||
|
||||
# ----------------------------------------------- WebSocket Initialization ---------------------------------------------------------------
|
||||
|
||||
socketio = SocketIO(api, cors_allowed_origins="*") # В production настроить CORS строже
|
||||
|
||||
graph_history_manager.set_socketio(socketio)
|
||||
|
||||
# ----------------------------------------------- WebSocket Events ---------------------------------------------------------------
|
||||
|
||||
@socketio.on('connect')
|
||||
def handle_connect():
|
||||
"""Обработчик подключения клиента к WebSocket."""
|
||||
print(f"Client connected: {request.sid}")
|
||||
|
||||
@socketio.on('disconnect')
|
||||
def handle_disconnect():
|
||||
"""Обработчик отключения клиента от WebSocket."""
|
||||
print(f"Client disconnected: {request.sid}")
|
||||
|
||||
# ----------------------------------------------- API Endpoints ---------------------------------------------------------------
|
||||
|
||||
@api.route('/api/chat', methods=['POST'])
|
||||
def chat():
|
||||
"""API endpoint для обработки сообщений и возврата ответа."""
|
||||
|
|
@ -184,12 +205,15 @@ def regenerate_message():
|
|||
|
||||
try:
|
||||
# Определяем родительский узел
|
||||
parent_node_id = graph_history_manager.get_parent_node_id(graph_id, node_id)
|
||||
parent_node_id = graph_history_manager.get_parent_node_id(
|
||||
graph_id, node_id)
|
||||
if not parent_node_id:
|
||||
return jsonify({"error": "Не удалось найти родительский узел."}, 404)
|
||||
return jsonify({"error": "Не удалось найти родительский узел."},
|
||||
404)
|
||||
|
||||
# Создаём новый узел-заглушку для регенерации
|
||||
new_assistant_node_id = graph_history_manager.create_assistant_placeholder_node(graph_id, parent_node_id)
|
||||
new_assistant_node_id = graph_history_manager.create_assistant_placeholder_node(
|
||||
graph_id, parent_node_id)
|
||||
|
||||
return jsonify({
|
||||
"new_node_id": new_assistant_node_id,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@ class GraphHistoryManager:
|
|||
self.title_generator = TitleGenerator(self)
|
||||
self.title_generator.start()
|
||||
|
||||
self.socketio = None
|
||||
|
||||
def set_socketio(self, socketio):
|
||||
socketio = socketio
|
||||
self.title_generator.socketio = socketio
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
Деструктор класса. Останавливает генератор заголовков при уничтожении объекта.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ class TitleGenerator:
|
|||
self.running = False
|
||||
self.thread: Optional[threading.Thread] = None
|
||||
|
||||
self.socketio = None
|
||||
|
||||
# Системные промпты для генерации заголовков
|
||||
self.graph_title_prompt = """Создай краткий заголовок (максимум 80 символов) для диалога на основе первого сообщения пользователя.
|
||||
Заголовок должен отражать основную тему или вопрос. Заголовок должен быть простым текстом, без какого-либо форматирования или использования
|
||||
|
|
@ -185,6 +187,14 @@ class TitleGenerator:
|
|||
self.history_manager.update_graph_title(graph_id, title)
|
||||
print(f"Сгенерирован заголовок графа {graph_id}: {title}")
|
||||
|
||||
# ----------------------------------------------- WebSocket Notification ---------------------------------------------------------------
|
||||
# Отправляем событие через WebSocket
|
||||
if self.socketio:
|
||||
self.socketio.emit('graph_title_updated', {
|
||||
'graph_id': graph_id,
|
||||
'title': title
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка генерации заголовка графа {graph_id}: {e}")
|
||||
|
||||
|
|
@ -230,22 +240,18 @@ class TitleGenerator:
|
|||
self.history_manager.update_node_title(graph_id, node_id, title)
|
||||
print(f"Сгенерирован заголовок узла {node_id}: {title}")
|
||||
|
||||
# ----------------------------------------------- WebSocket Notification ---------------------------------------------------------------
|
||||
# Отправляем событие через WebSocket
|
||||
if self.socketio:
|
||||
self.socketio.emit('node_title_updated', {
|
||||
'graph_id': graph_id,
|
||||
'node_id': node_id,
|
||||
'title': title
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка генерации заголовка узла {node_id}: {e}")
|
||||
|
||||
'''def add_to_title_queue(self,
|
||||
item_type: str,
|
||||
graph_id: str,
|
||||
node_id: str = None,
|
||||
priority: int = 0):
|
||||
"""Добавляет элемент в очередь генерации заголовков."""
|
||||
with self._get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"INSERT INTO title_generation_queue (item_type, graph_id, node_id, priority) VALUES (?, ?, ?, ?)",
|
||||
(item_type, graph_id, node_id, priority))
|
||||
conn.commit()'''
|
||||
|
||||
def add_node_to_queue(self, cursor, graph_id, node_id):
|
||||
# Добавляем в очередь заголовков с высоким приоритетом
|
||||
cursor.execute(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from langgraph.graph import StateGraph, END
|
|||
from graph_history_manager import GraphHistoryManager
|
||||
from models import AgentState
|
||||
from nodes import parse_command_node, execute_command_node, call_llm_node, generate_images_node, analyze_image_node, get_meet_subtitles_node, get_teams_subtitles_node, summarize_history_node, handle_error_node, help_node
|
||||
import uuid # Добавлено для генерации UUID
|
||||
import uuid # Добавлено для генерации UUID
|
||||
from llm_client import get_llm
|
||||
# --- LangGraph: Построение графа ---
|
||||
|
||||
|
|
@ -103,12 +103,10 @@ def run_agent(user_input: str,
|
|||
"""
|
||||
|
||||
# ---------------------------------------------------- Initial State Setup ----------------------------------------------------------------
|
||||
initial_state = AgentState(
|
||||
input=user_input,
|
||||
parent_node_id=parent_node_id,
|
||||
system_prompt=system_prompt,
|
||||
selected_model=model or "gemini-2.5-flash"
|
||||
)
|
||||
initial_state = AgentState(input=user_input,
|
||||
parent_node_id=parent_node_id,
|
||||
system_prompt=system_prompt,
|
||||
selected_model=model or "gemini-2.5-flash")
|
||||
|
||||
# Сохраняем исходные ID узлов и ребер для определения новых после выполнения графа
|
||||
original_node_ids: Set[str] = set()
|
||||
|
|
@ -125,26 +123,35 @@ def run_agent(user_input: str,
|
|||
if loaded_graph_data:
|
||||
print(f"Продолжаю существующий граф {existing_graph_id}")
|
||||
# Загружаем узлы и ребра для продолжения графа
|
||||
initial_state.graph_nodes = loaded_graph_data.get("graph_nodes", [])
|
||||
initial_state.graph_edges = loaded_graph_data.get("graph_edges", [])
|
||||
initial_state.graph_nodes = loaded_graph_data.get(
|
||||
"graph_nodes", [])
|
||||
initial_state.graph_edges = loaded_graph_data.get(
|
||||
"graph_edges", [])
|
||||
|
||||
# Сохраняем ID загруженных узлов и ребер
|
||||
original_node_ids = {node['id'] for node in initial_state.graph_nodes}
|
||||
original_edge_ids = {edge['id'] for edge in initial_state.graph_edges}
|
||||
original_node_ids = {
|
||||
node['id']
|
||||
for node in initial_state.graph_nodes
|
||||
}
|
||||
original_edge_ids = {
|
||||
edge['id']
|
||||
for edge in initial_state.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")
|
||||
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", [])
|
||||
initial_state.temporary_chat_history = loaded_graph_data.get(
|
||||
"messages", [])
|
||||
|
||||
# Определяем graph_id, который будет использоваться для сохранения.
|
||||
# Если существующий ID не передан, GraphHistoryManager сгенерирует новый.
|
||||
graph_id_to_save = existing_graph_id
|
||||
|
||||
|
||||
# ---------------------------------------------------- run_agent ----------------------------------------------------------------
|
||||
# Запускаем граф
|
||||
result = app.invoke(initial_state)
|
||||
|
|
@ -152,18 +159,21 @@ def run_agent(user_input: str,
|
|||
|
||||
# ---------------------------------------------------- Save Graph ----------------------------------------------------------------
|
||||
# Определяем новые узлы и ребра для сохранения
|
||||
newly_added_nodes = [node for node in final_state.graph_nodes if node['id'] not in original_node_ids]
|
||||
newly_added_edges = [edge for edge in final_state.graph_edges if edge['id'] not in original_edge_ids]
|
||||
newly_added_nodes = [
|
||||
node for node in final_state.graph_nodes
|
||||
if node['id'] not in original_node_ids
|
||||
]
|
||||
newly_added_edges = [
|
||||
edge for edge in final_state.graph_edges
|
||||
if edge['id'] not in original_edge_ids
|
||||
]
|
||||
|
||||
# Сохраняем обновленное состояние графа.
|
||||
# Обратите внимание: chat_history больше не сохраняется напрямую,
|
||||
# она является частью данных узлов в graph_nodes.
|
||||
final_graph_id = graph_history_manager.save_graph_changes(
|
||||
graph_id_to_save,
|
||||
newly_added_nodes,
|
||||
newly_added_edges,
|
||||
final_state.current_node_id
|
||||
)
|
||||
graph_id_to_save, newly_added_nodes, newly_added_edges,
|
||||
final_state.current_node_id)
|
||||
|
||||
# ---------------------------------------------------- prepare response ----------------------------------------------------------------
|
||||
# Для `messages` в `response_data` используем `final_state.temporary_chat_history`,
|
||||
|
|
@ -185,14 +195,20 @@ def run_agent(user_input: str,
|
|||
}
|
||||
return response_data
|
||||
|
||||
def run_agent_streaming(graph_id: str, user_node_id: str, assistant_node_id: str, system_prompt: Optional[str] = None, model: Optional[str] = None):
|
||||
|
||||
def run_agent_streaming(graph_id: str,
|
||||
user_node_id: str,
|
||||
assistant_node_id: str,
|
||||
system_prompt: Optional[str] = None,
|
||||
model: Optional[str] = None):
|
||||
"""
|
||||
Генератор для стримингового ответа LLM.
|
||||
Возвращает чанки контента по мере их получения.
|
||||
"""
|
||||
try:
|
||||
# Загружаем граф и историю до user_node_id
|
||||
loaded_graph_data = graph_history_manager.get_graph(graph_id, target_node_id=user_node_id)
|
||||
loaded_graph_data = graph_history_manager.get_graph(
|
||||
graph_id, target_node_id=user_node_id)
|
||||
if not loaded_graph_data:
|
||||
yield {"type": "error", "error": "Граф не найден."}
|
||||
return
|
||||
|
|
@ -216,7 +232,8 @@ def run_agent_streaming(graph_id: str, user_node_id: str, assistant_node_id: str
|
|||
llm = get_llm(model or "gemini-2.5-flash")
|
||||
|
||||
for chunk in llm.stream2(messages_for_llm):
|
||||
content = chunk.content if hasattr(chunk, 'content') else str(chunk)
|
||||
content = chunk.content if hasattr(chunk,
|
||||
'content') else str(chunk)
|
||||
yield {"type": "chunk", "content": content}
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -6,5 +6,6 @@ langchain_google_genai
|
|||
|
||||
flask
|
||||
flask_cors
|
||||
flask_socketio
|
||||
|
||||
mistralai
|
||||
Loading…
Reference in New Issue
Block a user