添加测试、添加总结、全量、rag(todo)3种历史记录保存方式,流式实现

This commit is contained in:
2026-05-05 03:01:20 +08:00
parent 2050a30a52
commit adb59da06d
46 changed files with 4484 additions and 882 deletions

View File

@@ -0,0 +1,154 @@
"""
聊天总结 API 路由
处理聊天记录的总结请求
"""
import logging
from typing import Dict, Any
from fastapi import APIRouter, HTTPException, Body
from fastapi.responses import JSONResponse
from services.chat_service import chat_service
from services.chat_summary_service import chat_summary_service
from models.internal import SummaryConfig
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/chats", tags=["chat-summary"])
@router.post("/{role_name}/{chat_name}/summarize")
async def summarize_chat_history(
role_name: str,
chat_name: str,
request_data: Dict[str, Any] = Body(...)
):
"""
总结聊天历史记录
Args:
role_name: 角色名称
chat_name: 聊天名称
request_data: {
"startFloor": int, # 总结起始楼层
"endFloor": int, # 总结结束楼层
"summaryConfig": {...}, # 总结配置
"apiConfig": {...} # API配置
}
Returns:
{
"success": bool,
"summaryText": str, # 总结文本
"startFloor": int,
"endFloor": int,
"message": str
}
"""
try:
# 1. 提取请求参数
start_floor = request_data.get("startFloor")
end_floor = request_data.get("endFloor")
summary_config_data = request_data.get("summaryConfig", {})
api_config = request_data.get("apiConfig", {})
if not start_floor or not end_floor:
raise HTTPException(status_code=400, detail="缺少 startFloor 或 endFloor 参数")
# 2. 加载聊天记录
chat_log = chat_service.get_chat_log(role_name, chat_name)
if not chat_log:
raise HTTPException(status_code=404, detail=f"聊天记录 '{role_name}/{chat_name}' 不存在")
messages = chat_log.messages
total_messages = len(messages)
# 3. 验证楼层范围
if start_floor < 1 or end_floor > total_messages or start_floor > end_floor:
raise HTTPException(
status_code=400,
detail=f"无效的楼层范围: {start_floor}-{end_floor}(总共{total_messages}条消息)"
)
# 4. 构建SummaryConfig对象
summary_config = SummaryConfig(**summary_config_data)
logger.info(
f"[ChatSummary] 开始总结: {role_name}/{chat_name}, "
f"楼层范围: {start_floor}-{end_floor}, "
f"包含用户输入: {summary_config.includeUserInput}"
)
# 5. 调用总结服务
summary_text = await chat_summary_service.summarize_messages(
messages=messages,
start_floor=start_floor,
end_floor=end_floor,
summary_config=summary_config,
api_config=api_config
)
if not summary_text:
raise HTTPException(status_code=500, detail="总结生成失败")
logger.info(f"[ChatSummary] 总结完成,长度: {len(summary_text)} 字符")
# 6. 更新聊天记录(清空原文 + 替换总结)
chat_service.summarize_chat_messages(
role_name=role_name,
chat_name=chat_name,
start_floor=start_floor,
end_floor=end_floor,
summary_text=summary_text
)
return {
"success": True,
"summaryText": summary_text,
"startFloor": start_floor,
"endFloor": end_floor,
"message": f"成功总结 {end_floor - start_floor + 1} 条消息"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"[ChatSummary] 总结失败: {str(e)}")
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=f"总结失败: {str(e)}")
@router.get("/{role_name}/{chat_name}/summary-status")
async def get_summary_status(role_name: str, chat_name: str):
"""
获取聊天总结状态
Returns:
{
"historyMode": str,
"summaryCounter": int,
"lastSummaryFloor": int,
"summaryConfig": {...}
}
"""
try:
chat_log = chat_service.get_chat_log(role_name, chat_name)
if not chat_log:
raise HTTPException(status_code=404, detail="聊天记录不存在")
header = chat_log.header
return {
"historyMode": header.historyMode.value if hasattr(header.historyMode, 'value') else header.historyMode,
"summaryCounter": header.summaryCounter or 0,
"lastSummaryFloor": getattr(header, 'lastSummaryFloor', 0),
"summaryConfig": header.summaryConfig.dict() if header.summaryConfig else None
}
except HTTPException:
raise
except Exception as e:
logger.error(f"[ChatSummary] 获取总结状态失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -266,9 +266,11 @@ async def _handle_stream_chat(
# ✅ 发送激活的世界书条目信息在LLM调用前
if active_entries:
print(f"[StreamChat] 📤 发送世界书激活信息: {len(active_entries)} 个条目")
# 将 Pydantic 模型转换为字典
entries_dict = [entry.model_dump() for entry in active_entries]
await websocket.send_json({
"type": "worldbook_active",
"entries": active_entries
"entries": entries_dict
})
# ✅ TODO: RAG检索暂时为空待实现
@@ -310,7 +312,7 @@ async def _handle_stream_chat(
})
# ✅ 第3步调用LLM流式生成
chunk_count = 0
chunk_count = [0] # 使用列表以便在闭包中修改
result = await workflow_service.process_chat_request_stream(
request_data,
on_chunk=lambda chunk: asyncio.create_task(

View File

@@ -40,19 +40,45 @@ async def get_preset(preset_name: str):
raise HTTPException(status_code=500, detail=str(e))
@router.post("", status_code=status.HTTP_201_CREATED)
async def create_preset(preset_name: str, preset_data: dict):
async def create_preset(preset_data: dict):
"""创建新预设"""
raise HTTPException(status_code=501, detail="Not Implemented")
try:
preset_name = preset_data.get("name")
if not preset_name:
raise HTTPException(status_code=400, detail="preset name is required")
# 使用 create_preset 方法保存预设
saved_preset = PresetService.create_preset(preset_name, preset_data)
return {"success": True, "preset": saved_preset}
except ValueError as e:
raise HTTPException(status_code=409, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/{preset_name}")
async def update_preset(preset_name: str, update_data: dict):
"""更新预设配置"""
raise HTTPException(status_code=501, detail="Not Implemented")
try:
updated_preset = PresetService.update_preset(preset_name, update_data)
return {"success": True, "preset": updated_preset}
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{preset_name}")
async def delete_preset(preset_name: str):
"""删除指定预设"""
raise HTTPException(status_code=501, detail="Not Implemented")
try:
success = PresetService.delete_preset(preset_name)
if success:
return {"success": True, "message": f"Preset '{preset_name}' deleted"}
else:
raise HTTPException(status_code=404, detail="Preset not found")
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{preset_name}/components", status_code=status.HTTP_201_CREATED)
async def add_preset_component(preset_name: str, component_data: dict):
@@ -68,3 +94,18 @@ async def update_preset_component(preset_name: str, component_id: str, update_da
async def delete_preset_component(preset_name: str, component_id: str):
"""从预设中删除指定组件"""
raise HTTPException(status_code=501, detail="Not Implemented")
@router.post("/{preset_name}/reorder")
async def reorder_preset_components(preset_name: str, order_data: dict):
"""重新排序预设组件"""
try:
component_order = order_data.get("component_order", [])
if not component_order:
raise HTTPException(status_code=400, detail="component_order is required")
updated_preset = PresetService.reorder_components(preset_name, component_order)
return {"success": True, "preset": updated_preset}
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))