Files
PlantAI-dev/gfa_core/mas/api/conversation_routes.py
2026-08-03 18:42:47 +08:00

375 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Conversation API Routes - 多会话管理REST API
类似ChatGPT的会话管理功能
"""
import asyncio
from typing import Optional
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
from datetime import datetime
from gfa_core.mas.memory.conversation import ConversationManager
router = APIRouter()
# 全局ConversationManager实例
_conversation_manager: Optional[ConversationManager] = None
def get_conversation_manager() -> ConversationManager:
"""获取ConversationManager实例(单例)"""
global _conversation_manager
if _conversation_manager is None:
_conversation_manager = ConversationManager()
return _conversation_manager
# ============ Pydantic Models ============
class CreateConversationRequest(BaseModel):
"""创建会话请求"""
user_id: str
title: Optional[str] = None
gene_family: Optional[str] = None
species: Optional[str] = None
system_prompt: Optional[str] = None
job_id: Optional[str] = None
input_dir: Optional[str] = None
output_dir: Optional[str] = None
working_dir: Optional[str] = None
class CreateConversationResponse(BaseModel):
"""创建会话响应"""
conversation_id: str
title: str
created_at: str
message: str
class AddMessageRequest(BaseModel):
"""添加消息请求"""
conversation_id: str
role: str # "user" | "assistant" | "system"
content: str
metadata: Optional[dict] = None
class AddMessageResponse(BaseModel):
"""添加消息响应"""
success: bool
message: str
class ConversationSummary(BaseModel):
"""会话摘要"""
conversation_id: str
title: str
last_message: str
updated_at: str
message_count: int
gene_family: Optional[str] = None
job_id: Optional[str] = None
input_dir: Optional[str] = None
output_dir: Optional[str] = None
working_dir: Optional[str] = None
class ListConversationsResponse(BaseModel):
"""列会话响应"""
user_id: str
conversations: list[ConversationSummary]
total: int
class MessageItem(BaseModel):
"""消息项"""
role: str
content: str
timestamp: str
metadata: Optional[dict] = None
class GetMessagesResponse(BaseModel):
"""获取消息响应"""
conversation_id: str
title: str
messages: list[MessageItem]
total: int
class UpdateTitleRequest(BaseModel):
"""更新标题请求"""
conversation_id: str
title: str
class DeleteConversationRequest(BaseModel):
"""删除会话请求"""
conversation_id: str
class LinkToJobRequest(BaseModel):
"""关联到Job请求"""
conversation_id: str
job_id: str
# ============ API Routes ============
@router.post("/conversations/create", response_model=CreateConversationResponse)
async def create_conversation(request: CreateConversationRequest):
"""
创建新会话
类似ChatGPT的"New Chat"功能
"""
try:
manager = get_conversation_manager()
conversation = await manager.create_conversation(
user_id=request.user_id,
title=request.title,
gene_family=request.gene_family,
species=request.species,
system_prompt=request.system_prompt,
job_id=request.job_id,
input_dir=request.input_dir,
output_dir=request.output_dir,
working_dir=request.working_dir,
)
return CreateConversationResponse(
conversation_id=conversation.conversation_id,
title=conversation.title,
created_at=conversation.created_at,
message="会话创建成功"
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"创建会话失败: {str(e)}")
@router.post("/conversations/message", response_model=AddMessageResponse)
async def add_message(request: AddMessageRequest):
"""
添加消息到会话
发送消息或接收AI回复
"""
try:
manager = get_conversation_manager()
success = await manager.add_message(
conversation_id=request.conversation_id,
role=request.role,
content=request.content,
metadata=request.metadata,
)
if not success:
raise HTTPException(status_code=404, detail="会话不存在")
return AddMessageResponse(
success=True,
message="消息添加成功"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"添加消息失败: {str(e)}")
@router.get("/conversations/list/{user_id}", response_model=ListConversationsResponse)
async def list_conversations(
user_id: str,
status: str = Query(default="active", description="会话状态: active/archived/deleted"),
limit: int = Query(default=20, ge=1, le=100),
offset: int = Query(default=0, ge=0),
):
"""
列出用户的所有会话
类似ChatGPT左侧边栏的会话列表
"""
try:
manager = get_conversation_manager()
conversations = await manager.list_user_conversations(
user_id=user_id,
status=status,
limit=limit,
offset=offset,
)
return ListConversationsResponse(
user_id=user_id,
conversations=[
ConversationSummary(
conversation_id=c.conversation_id,
title=c.title,
last_message=c.last_message,
updated_at=c.updated_at,
message_count=c.message_count,
gene_family=c.gene_family,
job_id=c.job_id,
input_dir=c.input_dir,
output_dir=c.output_dir,
working_dir=c.working_dir,
)
for c in conversations
],
total=len(conversations)
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取会话列表失败: {str(e)}")
@router.get("/conversations/{conversation_id}/messages", response_model=GetMessagesResponse)
async def get_messages(
conversation_id: str,
limit: int = Query(default=100, ge=1, le=500),
offset: int = Query(default=0, ge=0),
):
"""
获取会话的消息历史
查看某个会话的完整对话
"""
try:
manager = get_conversation_manager()
# 获取会话信息
conversation = await manager.get_conversation(conversation_id)
if not conversation:
raise HTTPException(status_code=404, detail="会话不存在")
# 默认返回最近 limit 条消息;显式 offset 用于从旧消息开始分页。
total_messages = len(conversation.messages or [])
if offset == 0:
messages = (conversation.messages or [])[-limit:]
else:
messages = await manager.get_messages(
conversation_id=conversation_id,
limit=limit,
offset=offset,
)
return GetMessagesResponse(
conversation_id=conversation_id,
title=conversation.title,
messages=[
MessageItem(
role=m["role"],
content=m["content"],
timestamp=m["timestamp"],
metadata=m.get("metadata"),
)
for m in messages
],
total=total_messages
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取消息失败: {str(e)}")
@router.post("/conversations/update-title")
async def update_title(request: UpdateTitleRequest):
"""
更新会话标题
重命名会话
"""
try:
manager = get_conversation_manager()
success = await manager.update_title(
conversation_id=request.conversation_id,
title=request.title,
)
if not success:
raise HTTPException(status_code=404, detail="会话不存在")
return {"success": True, "message": "标题更新成功"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"更新标题失败: {str(e)}")
@router.post("/conversations/delete")
async def delete_conversation(request: DeleteConversationRequest):
"""
删除会话
软删除,会话进入deleted状态
"""
try:
manager = get_conversation_manager()
success = await manager.delete_conversation(
conversation_id=request.conversation_id,
)
if not success:
raise HTTPException(status_code=404, detail="会话不存在")
return {"success": True, "message": "会话已删除"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"删除会话失败: {str(e)}")
@router.post("/conversations/archive")
async def archive_conversation(request: DeleteConversationRequest):
"""
归档会话
会话进入archived状态
"""
try:
manager = get_conversation_manager()
success = await manager.archive_conversation(
conversation_id=request.conversation_id,
)
if not success:
raise HTTPException(status_code=404, detail="会话不存在")
return {"success": True, "message": "会话已归档"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"归档会话失败: {str(e)}")
@router.post("/conversations/link-to-job")
async def link_to_job(request: LinkToJobRequest):
"""
关联会话到分析任务
将对话与具体的基因家族分析任务关联
"""
try:
manager = get_conversation_manager()
success = await manager.link_to_job(
conversation_id=request.conversation_id,
job_id=request.job_id,
)
if not success:
raise HTTPException(status_code=404, detail="会话不存在")
return {"success": True, "message": "已关联到分析任务"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"关联任务失败: {str(e)}")