feat: AI聊天室多Agent协作讨论平台
- 实现Agent管理,支持AI辅助生成系统提示词 - 支持多个AI提供商(OpenRouter、智谱、MiniMax等) - 实现聊天室和讨论引擎 - WebSocket实时消息推送 - 前端使用React + Ant Design - 后端使用FastAPI + MongoDB Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
387
backend/routers/chatrooms.py
Normal file
387
backend/routers/chatrooms.py
Normal file
@@ -0,0 +1,387 @@
|
||||
"""
|
||||
聊天室管理路由
|
||||
"""
|
||||
from typing import List, Optional, Dict, Any
|
||||
from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect, status
|
||||
from pydantic import BaseModel, Field
|
||||
from loguru import logger
|
||||
|
||||
from services.chatroom_service import ChatRoomService
|
||||
from services.discussion_engine import DiscussionEngine
|
||||
from services.message_router import MessageRouter
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ============ 请求/响应模型 ============
|
||||
|
||||
class ChatRoomConfigModel(BaseModel):
|
||||
"""聊天室配置模型"""
|
||||
max_rounds: int = 50
|
||||
message_history_size: int = 20
|
||||
consensus_threshold: float = 0.8
|
||||
round_interval: float = 1.0
|
||||
allow_user_interrupt: bool = True
|
||||
|
||||
|
||||
class ChatRoomCreateRequest(BaseModel):
|
||||
"""创建聊天室请求"""
|
||||
name: str = Field(..., description="聊天室名称")
|
||||
description: str = Field(default="", description="描述")
|
||||
agents: List[str] = Field(default=[], description="Agent ID列表")
|
||||
moderator_agent_id: Optional[str] = Field(default=None, description="主持人Agent ID")
|
||||
config: Optional[ChatRoomConfigModel] = None
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"name": "产品设计讨论室",
|
||||
"description": "用于讨论新产品功能设计",
|
||||
"agents": ["agent-abc123", "agent-def456"],
|
||||
"moderator_agent_id": "agent-xyz789"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ChatRoomUpdateRequest(BaseModel):
|
||||
"""更新聊天室请求"""
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
agents: Optional[List[str]] = None
|
||||
moderator_agent_id: Optional[str] = None
|
||||
config: Optional[ChatRoomConfigModel] = None
|
||||
|
||||
|
||||
class ChatRoomResponse(BaseModel):
|
||||
"""聊天室响应"""
|
||||
room_id: str
|
||||
name: str
|
||||
description: str
|
||||
objective: str
|
||||
agents: List[str]
|
||||
moderator_agent_id: Optional[str]
|
||||
config: Dict[str, Any]
|
||||
status: str
|
||||
current_round: int
|
||||
current_discussion_id: Optional[str]
|
||||
created_at: str
|
||||
updated_at: str
|
||||
completed_at: Optional[str]
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
"""消息响应"""
|
||||
message_id: str
|
||||
room_id: str
|
||||
discussion_id: str
|
||||
agent_id: Optional[str]
|
||||
content: str
|
||||
message_type: str
|
||||
round: int
|
||||
created_at: str
|
||||
|
||||
|
||||
class StartDiscussionRequest(BaseModel):
|
||||
"""启动讨论请求"""
|
||||
objective: str = Field(..., description="讨论目标")
|
||||
|
||||
|
||||
class DiscussionStatusResponse(BaseModel):
|
||||
"""讨论状态响应"""
|
||||
is_active: bool
|
||||
room_id: str
|
||||
discussion_id: Optional[str] = None
|
||||
current_round: int = 0
|
||||
status: str
|
||||
|
||||
|
||||
# ============ 路由处理 ============
|
||||
|
||||
@router.post("", response_model=ChatRoomResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_chatroom(request: ChatRoomCreateRequest):
|
||||
"""
|
||||
创建新的聊天室
|
||||
"""
|
||||
try:
|
||||
chatroom = await ChatRoomService.create_chatroom(
|
||||
name=request.name,
|
||||
description=request.description,
|
||||
agents=request.agents,
|
||||
moderator_agent_id=request.moderator_agent_id,
|
||||
config=request.config.dict() if request.config else None
|
||||
)
|
||||
|
||||
return _to_response(chatroom)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"创建聊天室失败: {e}")
|
||||
raise HTTPException(status_code=500, detail="创建失败")
|
||||
|
||||
|
||||
@router.get("", response_model=List[ChatRoomResponse])
|
||||
async def list_chatrooms():
|
||||
"""
|
||||
获取所有聊天室
|
||||
"""
|
||||
chatrooms = await ChatRoomService.get_all_chatrooms()
|
||||
return [_to_response(c) for c in chatrooms]
|
||||
|
||||
|
||||
@router.get("/{room_id}", response_model=ChatRoomResponse)
|
||||
async def get_chatroom(room_id: str):
|
||||
"""
|
||||
获取指定聊天室
|
||||
"""
|
||||
chatroom = await ChatRoomService.get_chatroom(room_id)
|
||||
if not chatroom:
|
||||
raise HTTPException(status_code=404, detail="聊天室不存在")
|
||||
return _to_response(chatroom)
|
||||
|
||||
|
||||
@router.put("/{room_id}", response_model=ChatRoomResponse)
|
||||
async def update_chatroom(room_id: str, request: ChatRoomUpdateRequest):
|
||||
"""
|
||||
更新聊天室配置
|
||||
"""
|
||||
update_data = request.dict(exclude_unset=True)
|
||||
|
||||
if "config" in update_data and update_data["config"]:
|
||||
if hasattr(update_data["config"], "dict"):
|
||||
update_data["config"] = update_data["config"].dict()
|
||||
|
||||
try:
|
||||
chatroom = await ChatRoomService.update_chatroom(room_id, **update_data)
|
||||
if not chatroom:
|
||||
raise HTTPException(status_code=404, detail="聊天室不存在")
|
||||
return _to_response(chatroom)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.delete("/{room_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_chatroom(room_id: str):
|
||||
"""
|
||||
删除聊天室
|
||||
"""
|
||||
success = await ChatRoomService.delete_chatroom(room_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="聊天室不存在")
|
||||
|
||||
|
||||
@router.post("/{room_id}/agents/{agent_id}", response_model=ChatRoomResponse)
|
||||
async def add_agent_to_chatroom(room_id: str, agent_id: str):
|
||||
"""
|
||||
向聊天室添加Agent
|
||||
"""
|
||||
try:
|
||||
chatroom = await ChatRoomService.add_agent(room_id, agent_id)
|
||||
if not chatroom:
|
||||
raise HTTPException(status_code=404, detail="聊天室不存在")
|
||||
return _to_response(chatroom)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.delete("/{room_id}/agents/{agent_id}", response_model=ChatRoomResponse)
|
||||
async def remove_agent_from_chatroom(room_id: str, agent_id: str):
|
||||
"""
|
||||
从聊天室移除Agent
|
||||
"""
|
||||
chatroom = await ChatRoomService.remove_agent(room_id, agent_id)
|
||||
if not chatroom:
|
||||
raise HTTPException(status_code=404, detail="聊天室不存在")
|
||||
return _to_response(chatroom)
|
||||
|
||||
|
||||
@router.get("/{room_id}/messages", response_model=List[MessageResponse])
|
||||
async def get_chatroom_messages(
|
||||
room_id: str,
|
||||
limit: int = 50,
|
||||
skip: int = 0,
|
||||
discussion_id: Optional[str] = None
|
||||
):
|
||||
"""
|
||||
获取聊天室消息历史
|
||||
"""
|
||||
messages = await ChatRoomService.get_messages(
|
||||
room_id, limit, skip, discussion_id
|
||||
)
|
||||
return [_message_to_response(m) for m in messages]
|
||||
|
||||
|
||||
@router.post("/{room_id}/start", response_model=DiscussionStatusResponse)
|
||||
async def start_discussion(room_id: str, request: StartDiscussionRequest):
|
||||
"""
|
||||
启动讨论
|
||||
"""
|
||||
try:
|
||||
# 异步启动讨论(不等待完成)
|
||||
import asyncio
|
||||
asyncio.create_task(
|
||||
DiscussionEngine.start_discussion(room_id, request.objective)
|
||||
)
|
||||
|
||||
# 等待一小段时间让讨论初始化
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
chatroom = await ChatRoomService.get_chatroom(room_id)
|
||||
|
||||
return DiscussionStatusResponse(
|
||||
is_active=True,
|
||||
room_id=room_id,
|
||||
discussion_id=chatroom.current_discussion_id if chatroom else None,
|
||||
current_round=chatroom.current_round if chatroom else 0,
|
||||
status=chatroom.status if chatroom else "unknown"
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{room_id}/pause", response_model=DiscussionStatusResponse)
|
||||
async def pause_discussion(room_id: str):
|
||||
"""
|
||||
暂停讨论
|
||||
"""
|
||||
success = await DiscussionEngine.pause_discussion(room_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail="没有进行中的讨论")
|
||||
|
||||
chatroom = await ChatRoomService.get_chatroom(room_id)
|
||||
return DiscussionStatusResponse(
|
||||
is_active=False,
|
||||
room_id=room_id,
|
||||
discussion_id=chatroom.current_discussion_id if chatroom else None,
|
||||
current_round=chatroom.current_round if chatroom else 0,
|
||||
status="paused"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{room_id}/resume", response_model=DiscussionStatusResponse)
|
||||
async def resume_discussion(room_id: str):
|
||||
"""
|
||||
恢复讨论
|
||||
"""
|
||||
success = await DiscussionEngine.resume_discussion(room_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail="聊天室不在暂停状态")
|
||||
|
||||
chatroom = await ChatRoomService.get_chatroom(room_id)
|
||||
return DiscussionStatusResponse(
|
||||
is_active=True,
|
||||
room_id=room_id,
|
||||
discussion_id=chatroom.current_discussion_id if chatroom else None,
|
||||
current_round=chatroom.current_round if chatroom else 0,
|
||||
status="active"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{room_id}/stop", response_model=DiscussionStatusResponse)
|
||||
async def stop_discussion(room_id: str):
|
||||
"""
|
||||
停止讨论
|
||||
"""
|
||||
success = await DiscussionEngine.stop_discussion(room_id)
|
||||
|
||||
chatroom = await ChatRoomService.get_chatroom(room_id)
|
||||
return DiscussionStatusResponse(
|
||||
is_active=False,
|
||||
room_id=room_id,
|
||||
discussion_id=chatroom.current_discussion_id if chatroom else None,
|
||||
current_round=chatroom.current_round if chatroom else 0,
|
||||
status="stopping" if success else chatroom.status if chatroom else "unknown"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{room_id}/status", response_model=DiscussionStatusResponse)
|
||||
async def get_discussion_status(room_id: str):
|
||||
"""
|
||||
获取讨论状态
|
||||
"""
|
||||
chatroom = await ChatRoomService.get_chatroom(room_id)
|
||||
if not chatroom:
|
||||
raise HTTPException(status_code=404, detail="聊天室不存在")
|
||||
|
||||
is_active = DiscussionEngine.is_discussion_active(room_id)
|
||||
|
||||
return DiscussionStatusResponse(
|
||||
is_active=is_active,
|
||||
room_id=room_id,
|
||||
discussion_id=chatroom.current_discussion_id,
|
||||
current_round=chatroom.current_round,
|
||||
status=chatroom.status
|
||||
)
|
||||
|
||||
|
||||
# ============ WebSocket端点 ============
|
||||
|
||||
@router.websocket("/ws/{room_id}")
|
||||
async def chatroom_websocket(websocket: WebSocket, room_id: str):
|
||||
"""
|
||||
聊天室WebSocket连接
|
||||
"""
|
||||
# 验证聊天室存在
|
||||
chatroom = await ChatRoomService.get_chatroom(room_id)
|
||||
if not chatroom:
|
||||
await websocket.close(code=4004, reason="聊天室不存在")
|
||||
return
|
||||
|
||||
await MessageRouter.connect(room_id, websocket)
|
||||
|
||||
try:
|
||||
while True:
|
||||
# 保持连接,接收客户端消息(如心跳)
|
||||
data = await websocket.receive_text()
|
||||
|
||||
# 处理心跳
|
||||
if data == "ping":
|
||||
await websocket.send_text("pong")
|
||||
|
||||
except WebSocketDisconnect:
|
||||
await MessageRouter.disconnect(room_id, websocket)
|
||||
except Exception as e:
|
||||
logger.error(f"WebSocket错误: {e}")
|
||||
await MessageRouter.disconnect(room_id, websocket)
|
||||
|
||||
|
||||
# ============ 辅助函数 ============
|
||||
|
||||
def _to_response(chatroom) -> ChatRoomResponse:
|
||||
"""
|
||||
转换为响应模型
|
||||
"""
|
||||
return ChatRoomResponse(
|
||||
room_id=chatroom.room_id,
|
||||
name=chatroom.name,
|
||||
description=chatroom.description,
|
||||
objective=chatroom.objective,
|
||||
agents=chatroom.agents,
|
||||
moderator_agent_id=chatroom.moderator_agent_id,
|
||||
config=chatroom.config,
|
||||
status=chatroom.status,
|
||||
current_round=chatroom.current_round,
|
||||
current_discussion_id=chatroom.current_discussion_id,
|
||||
created_at=chatroom.created_at.isoformat(),
|
||||
updated_at=chatroom.updated_at.isoformat(),
|
||||
completed_at=chatroom.completed_at.isoformat() if chatroom.completed_at else None
|
||||
)
|
||||
|
||||
|
||||
def _message_to_response(message) -> MessageResponse:
|
||||
"""
|
||||
转换消息为响应模型
|
||||
"""
|
||||
return MessageResponse(
|
||||
message_id=message.message_id,
|
||||
room_id=message.room_id,
|
||||
discussion_id=message.discussion_id,
|
||||
agent_id=message.agent_id,
|
||||
content=message.content,
|
||||
message_type=message.message_type,
|
||||
round=message.round,
|
||||
created_at=message.created_at.isoformat()
|
||||
)
|
||||
Reference in New Issue
Block a user