Files
assist/src/web/blueprints/conversations.py

200 lines
8.9 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
"""
对话管理蓝图
处理对话相关的API路由
"""
from flask import Blueprint, request, jsonify
from src.core.database import db_manager
from src.core.models import Conversation
from src.core.query_optimizer import query_optimizer
from datetime import timedelta
conversations_bp = Blueprint('conversations', __name__, url_prefix='/api/conversations')
@conversations_bp.route('')
def get_conversations():
"""获取对话历史列表(分页)- 优化版"""
try:
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
search = request.args.get('search', '')
user_id = request.args.get('user_id', '')
date_filter = request.args.get('date_filter', '')
# 使用优化后的查询
result = query_optimizer.get_conversations_paginated(
page=page, per_page=per_page, search=search,
user_id=user_id, date_filter=date_filter
)
# 规范化移除不存在的user_id字段避免前端误用
for conv in result.get('conversations', []):
if 'user_id' in conv and conv['user_id'] is None:
conv.pop('user_id', None)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@conversations_bp.route('/<int:conversation_id>')
def get_conversation_detail(conversation_id):
"""获取对话详情"""
try:
with db_manager.get_session() as session:
conv = session.query(Conversation).filter(Conversation.id == conversation_id).first()
if not conv:
return jsonify({"error": "对话不存在"}), 404
# Conversation模型没有user_id字段这里用占位或由外层推断
return jsonify({
'success': True,
'id': conv.id,
'user_id': None,
'user_message': conv.user_message,
'assistant_response': conv.assistant_response,
'timestamp': conv.timestamp.isoformat() if conv.timestamp else None,
'response_time': conv.response_time,
'work_order_id': conv.work_order_id
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@conversations_bp.route('/<int:conversation_id>', methods=['DELETE'])
def delete_conversation(conversation_id):
"""删除对话记录"""
try:
with db_manager.get_session() as session:
conv = session.query(Conversation).filter(Conversation.id == conversation_id).first()
if not conv:
return jsonify({"error": "对话不存在"}), 404
session.delete(conv)
session.commit()
# 清除对话历史相关缓存
from src.core.cache_manager import cache_manager
cache_manager.clear() # 清除所有缓存
return jsonify({"success": True, "message": "对话记录已删除"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@conversations_bp.route('/clear', methods=['DELETE'])
def clear_all_conversations():
"""清空所有对话历史"""
try:
with db_manager.get_session() as session:
session.query(Conversation).delete()
session.commit()
# 清除对话历史相关缓存
from src.core.cache_manager import cache_manager
cache_manager.clear() # 清除所有缓存
return jsonify({"success": True, "message": "对话历史已清空"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@conversations_bp.route('/migrate-merge', methods=['POST'])
def migrate_merge_conversations():
"""一次性迁移:将历史上拆分存储的用户/助手两条记录合并为一条
规则
- 只处理一端为空的记录user_only assistant_only
- 优先将 user_only 与其后最近的 assistant_only 合并同工单且5分钟内
- 若当前为 assistant_only 且前一条是 user_only 也合并到前一条
- 合并后删除被吸收的那条记录
- 可重复执行幂等已合并的不再满足一端为空的条件
"""
try:
merged_pairs = 0
deleted_rows = 0
time_threshold_seconds = 300
to_delete_ids = []
with db_manager.get_session() as session:
conversations = session.query(Conversation).order_by(Conversation.timestamp.asc(), Conversation.id.asc()).all()
total = len(conversations)
i = 0
def is_empty(text: str) -> bool:
return (text is None) or (str(text).strip() == '')
while i < total:
c = conversations[i]
user_only = (not is_empty(c.user_message)) and is_empty(c.assistant_response)
assistant_only = (not is_empty(c.assistant_response)) and is_empty(c.user_message)
if user_only:
# 向后寻找匹配的assistant_only
j = i + 1
while j < total:
n = conversations[j]
# 跳过已经标记删除的
if n.id in to_delete_ids:
j += 1
continue
# 超过阈值不再尝试
if c.timestamp and n.timestamp and (n.timestamp - c.timestamp).total_seconds() > time_threshold_seconds:
break
# 同工单或两者都为空均可
same_wo = (c.work_order_id == n.work_order_id) or (c.work_order_id is None and n.work_order_id is None)
if same_wo and (not is_empty(n.assistant_response)) and is_empty(n.user_message):
# 合并
c.assistant_response = n.assistant_response
if c.response_time is None and c.timestamp and n.timestamp:
try:
c.response_time = max(0.0, (n.timestamp - c.timestamp).total_seconds() * 1000.0)
except Exception:
pass
# 继承辅助信息
if (not c.confidence_score) and n.confidence_score is not None:
c.confidence_score = n.confidence_score
if (not c.knowledge_used) and n.knowledge_used:
c.knowledge_used = n.knowledge_used
session.add(c)
to_delete_ids.append(n.id)
merged_pairs += 1
break
j += 1
elif assistant_only:
# 向前与最近的 user_only 合并(如果尚未被其他合并吸收)
j = i - 1
while j >= 0:
p = conversations[j]
if p.id in to_delete_ids:
j -= 1
continue
if p.timestamp and c.timestamp and (c.timestamp - p.timestamp).total_seconds() > time_threshold_seconds:
break
same_wo = (c.work_order_id == p.work_order_id) or (c.work_order_id is None and p.work_order_id is None)
if same_wo and (not is_empty(p.user_message)) and is_empty(p.assistant_response):
p.assistant_response = c.assistant_response
if p.response_time is None and p.timestamp and c.timestamp:
try:
p.response_time = max(0.0, (c.timestamp - p.timestamp).total_seconds() * 1000.0)
except Exception:
pass
if (not p.confidence_score) and c.confidence_score is not None:
p.confidence_score = c.confidence_score
if (not p.knowledge_used) and c.knowledge_used:
p.knowledge_used = c.knowledge_used
session.add(p)
to_delete_ids.append(c.id)
merged_pairs += 1
break
j -= 1
i += 1
if to_delete_ids:
deleted_rows = session.query(Conversation).filter(Conversation.id.in_(to_delete_ids)).delete(synchronize_session=False)
session.commit()
return jsonify({
'success': True,
'merged_pairs': merged_pairs,
'deleted_rows': deleted_rows
})
except Exception as e:
return jsonify({"error": str(e)}), 500