refactor(session): 重构旧会话管理逻辑以提升可维护性
This commit is contained in:
parent
bfec192158
commit
315b3776cf
File diff suppressed because it is too large
Load Diff
|
|
@ -1,150 +0,0 @@
|
|||
"""
|
||||
GLM-4.6V 平台路由处理器(zai-sdk)
|
||||
所有智谱 GLM 相关逻辑均集中在此文件,main.py 无感知任何平台细节。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import HTTPException
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from utils.helpers import generate_unique_id, get_current_timestamp
|
||||
from utils.logger import log_info
|
||||
|
||||
|
||||
def init():
|
||||
"""
|
||||
初始化 GLM 后端:验证 API Key 是否配置。
|
||||
由 main.py 在启动时调用(若 LLM_BACKEND=glm)。
|
||||
"""
|
||||
api_key = os.getenv("ZHIPU_API_KEY") or os.getenv("GLM_API_KEY")
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
"GLM 模式需要设置环境变量 ZHIPU_API_KEY(在 https://open.bigmodel.cn 申请)"
|
||||
)
|
||||
log_info(f"[GLM] 初始化完成,ZHIPU_API_KEY 已配置")
|
||||
|
||||
|
||||
async def chat_handler(body: dict):
|
||||
"""
|
||||
GLM 聊天处理器(对外接口与百炼 chat_endpoint_handler 完全兼容)。
|
||||
流式/非流式自动适配,支持图像、文档附件、联网搜索、深度思考。
|
||||
"""
|
||||
from utils.glm_adapter import glm_chat_sync, glm_stream_generator
|
||||
|
||||
if not isinstance(body, dict):
|
||||
raise HTTPException(status_code=400, detail="请求体必须是 JSON 对象")
|
||||
|
||||
messages = body.get("messages", [])
|
||||
model = body.get("model", "glm-4.6v")
|
||||
stream = body.get("stream", True)
|
||||
temperature = body.get("temperature", 0.7)
|
||||
max_tokens = body.get("max_tokens", body.get("maxTokens", 2000))
|
||||
# 区分搜索模式:深度搜索 > 简单搜索 > 不搜索
|
||||
if body.get("deepSearch", False):
|
||||
web_search = "deep"
|
||||
elif body.get("webSearch", False):
|
||||
web_search = "simple"
|
||||
else:
|
||||
web_search = False
|
||||
deep_think = body.get("deepThinking", False)
|
||||
files = body.get("files", [])
|
||||
|
||||
# 兼容前端简化格式(非 messages 结构)
|
||||
if not messages:
|
||||
msg_text = body.get("message", "")
|
||||
sys_prompt = body.get("systemPrompt", "你是一个智能助手。")
|
||||
user_content = (
|
||||
msg_text
|
||||
if isinstance(msg_text, list)
|
||||
else [{"type": "text", "text": msg_text}]
|
||||
)
|
||||
messages = [
|
||||
{"role": "system", "content": sys_prompt},
|
||||
{"role": "user", "content": user_content},
|
||||
]
|
||||
|
||||
log_info(
|
||||
f"[GLM] model={model} stream={stream} web_search={web_search} "
|
||||
f"thinking={deep_think} files={len(files)} msgs={len(messages)}"
|
||||
)
|
||||
|
||||
if stream:
|
||||
return StreamingResponse(
|
||||
glm_stream_generator(
|
||||
messages=messages,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
files=files or None,
|
||||
web_search=web_search,
|
||||
deep_thinking=deep_think,
|
||||
),
|
||||
media_type="text/event-stream",
|
||||
)
|
||||
|
||||
result = glm_chat_sync(
|
||||
messages=messages,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
files=files or None,
|
||||
web_search=web_search,
|
||||
deep_thinking=deep_think,
|
||||
)
|
||||
resp = {
|
||||
"id": f"chatcmpl-{generate_unique_id()}",
|
||||
"object": "chat.completion",
|
||||
"created": get_current_timestamp(),
|
||||
"model": result["model"],
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {"role": "assistant", "content": result["content"]},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
}
|
||||
if result.get("usage"):
|
||||
resp["usage"] = result["usage"]
|
||||
return JSONResponse(content=resp)
|
||||
|
||||
|
||||
def models_handler():
|
||||
"""返回 GLM 可用模型列表"""
|
||||
return {
|
||||
"data": [
|
||||
{
|
||||
"id": "glm-4.6v",
|
||||
"name": "GLM-4.6V(推荐)",
|
||||
"description": "最新旗舰模型,支持文本/图像/文档/深度思考",
|
||||
"maxTokens": 128000,
|
||||
"provider": "ZhipuAI",
|
||||
},
|
||||
{
|
||||
"id": "glm-4-flash",
|
||||
"name": "GLM-4 Flash",
|
||||
"description": "高性价比文本模型(0.2元/千token)",
|
||||
"maxTokens": 128000,
|
||||
"provider": "ZhipuAI",
|
||||
},
|
||||
{
|
||||
"id": "glm-4v-plus-0111",
|
||||
"name": "GLM-4V Plus",
|
||||
"description": "图像 + PDF/DOCX 原生多模态",
|
||||
"maxTokens": 128000,
|
||||
"provider": "ZhipuAI",
|
||||
},
|
||||
{
|
||||
"id": "glm-z1-flash",
|
||||
"name": "GLM-Z1 Flash",
|
||||
"description": "深度思考推理模型",
|
||||
"maxTokens": 128000,
|
||||
"provider": "ZhipuAI",
|
||||
},
|
||||
],
|
||||
"object": "list",
|
||||
}
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
"""
|
||||
会话管理路由 - 对话 CRUD 和文件上传
|
||||
|
||||
与平台无关的通用功能,不涉及任何 LLM 平台细节。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import File, HTTPException, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
import sys
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
|
||||
from database import get_db
|
||||
from utils.helpers import generate_unique_id
|
||||
from utils.logger import log_error, log_exception, log_info
|
||||
|
||||
# 配置上传目录
|
||||
upload_dir = Path(__file__).parent.parent / "uploads"
|
||||
upload_dir.mkdir(exist_ok=True)
|
||||
|
||||
|
||||
# ── 会话管理 ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def get_conversations_handler():
|
||||
"""获取所有对话处理器"""
|
||||
db = get_db()
|
||||
return db.list_conversations()
|
||||
|
||||
|
||||
async def get_conversation_handler(conversation_id: str):
|
||||
"""获取特定对话处理器"""
|
||||
db = get_db()
|
||||
conversation = db.get_conversation(conversation_id)
|
||||
if not conversation:
|
||||
raise HTTPException(status_code=404, detail="对话不存在")
|
||||
return conversation
|
||||
|
||||
|
||||
async def save_conversation_handler(data: dict):
|
||||
"""保存或更新对话处理器"""
|
||||
try:
|
||||
db = get_db()
|
||||
conversation_id = data.get("id")
|
||||
|
||||
# 检查是否已存在
|
||||
existing = db.get_conversation(conversation_id) if conversation_id else None
|
||||
|
||||
if existing:
|
||||
# 更新现有会话
|
||||
return db.update_conversation(conversation_id, data)
|
||||
else:
|
||||
# 创建新会话
|
||||
return db.create_conversation(data)
|
||||
|
||||
except Exception as e:
|
||||
log_error(f"Error saving conversation: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
async def delete_conversation_handler(conversation_id: str):
|
||||
"""删除对话处理器"""
|
||||
db = get_db()
|
||||
success = db.delete_conversation(conversation_id)
|
||||
if success:
|
||||
return {"success": True, "message": "删除成功"}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="对话不存在")
|
||||
|
||||
|
||||
# ── 文件上传 ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def upload_file_handler(file: UploadFile = File(...)):
|
||||
"""文件上传处理器"""
|
||||
try:
|
||||
# 允许的 MIME 类型(宽松策略)
|
||||
allowed_types = {
|
||||
# 图片
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
"image/bmp",
|
||||
"image/svg+xml",
|
||||
# 文本类
|
||||
"text/plain",
|
||||
"text/csv",
|
||||
"text/markdown",
|
||||
"text/html",
|
||||
"text/xml",
|
||||
"application/json",
|
||||
"application/xml",
|
||||
# PDF
|
||||
"application/pdf",
|
||||
# Office 文档
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
}
|
||||
|
||||
# 允许的扩展名(兜底:MIME 类型可能被浏览器误判)
|
||||
allowed_extensions = {
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".png",
|
||||
".gif",
|
||||
".webp",
|
||||
".bmp",
|
||||
".txt",
|
||||
".md",
|
||||
".csv",
|
||||
".json",
|
||||
".xml",
|
||||
".yaml",
|
||||
".yml",
|
||||
".log",
|
||||
".pdf",
|
||||
".doc",
|
||||
".docx",
|
||||
".xls",
|
||||
".xlsx",
|
||||
".ppt",
|
||||
".pptx",
|
||||
".py",
|
||||
".js",
|
||||
".ts",
|
||||
".html",
|
||||
".css",
|
||||
}
|
||||
|
||||
file_extension = Path(file.filename).suffix.lower()
|
||||
|
||||
if (
|
||||
file.content_type not in allowed_types
|
||||
and file_extension not in allowed_extensions
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"不支持的文件类型: {file.content_type}({file_extension})",
|
||||
)
|
||||
|
||||
# 生成唯一文件名
|
||||
unique_filename = f"{int(datetime.utcnow().timestamp())}_{generate_unique_id()}{file_extension}"
|
||||
file_path = upload_dir / unique_filename
|
||||
|
||||
# 保存文件到本地(临时缓存)
|
||||
content = await file.read()
|
||||
with open(file_path, "wb") as f:
|
||||
f.write(content)
|
||||
|
||||
# 文件关闭后再上传到 OSS
|
||||
from utils.oss_uploader import upload_file as oss_upload
|
||||
|
||||
oss_result = oss_upload(str(file_path))
|
||||
file_url = oss_result["url"]
|
||||
|
||||
# 返回文件信息
|
||||
result = {
|
||||
"url": file_url,
|
||||
"name": file.filename,
|
||||
"size": len(content),
|
||||
"mimeType": file.content_type,
|
||||
}
|
||||
|
||||
log_info(f"File uploaded: {result}")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
log_error(f"Upload error: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"上传失败: {str(e)}")
|
||||
|
||||
|
||||
def serve_upload_handler(filename: str):
|
||||
"""提供上传文件访问处理器"""
|
||||
file_path = upload_dir / filename
|
||||
if not file_path.exists():
|
||||
raise HTTPException(status_code=404, detail="文件不存在")
|
||||
|
||||
return FileResponse(str(file_path))
|
||||
|
||||
|
||||
# ── 停止生成 ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def stop_generation_handler(message_id: str = None):
|
||||
"""停止生成处理器"""
|
||||
message = (
|
||||
f"已发出停止指令,消息ID: {message_id}" if message_id else "已发出停止指令"
|
||||
)
|
||||
return {"success": True, "message": message}
|
||||
|
|
@ -45,28 +45,22 @@ from utils.logger import get_logger
|
|||
|
||||
logger = get_logger()
|
||||
|
||||
# ── 初始化数据库 ───────────────────────────────────────────────────────
|
||||
from database import init_db
|
||||
|
||||
init_db()
|
||||
|
||||
# ── 加载环境变量 ──────────────────────────────────────────────────────
|
||||
load_dotenv()
|
||||
|
||||
LLM_BACKEND = os.getenv("LLM_BACKEND", "dashscope").lower().strip()
|
||||
if LLM_BACKEND not in {"dashscope", "glm"}:
|
||||
logger.warning(f"未知的 LLM_BACKEND='{LLM_BACKEND}',回退到 dashscope")
|
||||
LLM_BACKEND = "dashscope"
|
||||
|
||||
# ── 动态加载平台模块 ──────────────────────────────────────────────────
|
||||
if LLM_BACKEND == "glm":
|
||||
import api.chat_routes_glm as _platform
|
||||
else:
|
||||
import api.chat_routes as _platform
|
||||
|
||||
_platform.init() # 各平台自行完成初始化(API Key 校验等)
|
||||
|
||||
# 通用路由处理器(文件上传、会话管理等,与平台无关,统一用百炼路由中的实现)
|
||||
from api.chat_routes import (delete_conversation_handler,
|
||||
get_conversation_handler,
|
||||
get_conversations_handler,
|
||||
save_conversation_handler, serve_upload_handler,
|
||||
stop_generation_handler, upload_file_handler)
|
||||
# ── 会话管理路由处理器 ────────────────────────────────────────────────
|
||||
from api.conversation_routes import (delete_conversation_handler,
|
||||
get_conversation_handler,
|
||||
get_conversations_handler,
|
||||
save_conversation_handler,
|
||||
serve_upload_handler,
|
||||
stop_generation_handler,
|
||||
upload_file_handler)
|
||||
|
||||
# ── OpenAI 兼容网关初始化 ───────────────────────────────────────────────
|
||||
from api.openai_gateway import init_adapters, router as openai_router
|
||||
|
|
@ -110,12 +104,11 @@ async def health_check():
|
|||
return {
|
||||
"status": "healthy",
|
||||
"version": "4.0.0",
|
||||
"default_backend": LLM_BACKEND,
|
||||
"available_providers": get_available_providers(),
|
||||
"endpoints": {
|
||||
"openai_compatible": "/v1/chat/completions",
|
||||
"legacy": "/api/chat-ui/chat",
|
||||
"models": "/v1/models",
|
||||
"conversations": "/api/chat-ui/conversations",
|
||||
},
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
|
|
@ -234,7 +227,6 @@ if __name__ == "__main__":
|
|||
print(f" 模型列表 : http://localhost:{port}/v1/models")
|
||||
print("-" * 60)
|
||||
print(f" 可用平台 : {', '.join(available) or '无(请配置 API Key)'}")
|
||||
print(f" 默认平台 : {LLM_BACKEND} (向后兼容模式)")
|
||||
print("-" * 60)
|
||||
print(" 使用方法:")
|
||||
print(" curl -X POST http://localhost:8000/v1/chat/completions \\")
|
||||
|
|
|
|||
Loading…
Reference in New Issue