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:
14
backend/routers/__init__.py
Normal file
14
backend/routers/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
API路由模块
|
||||
"""
|
||||
from . import providers
|
||||
from . import agents
|
||||
from . import chatrooms
|
||||
from . import discussions
|
||||
|
||||
__all__ = [
|
||||
"providers",
|
||||
"agents",
|
||||
"chatrooms",
|
||||
"discussions",
|
||||
]
|
||||
314
backend/routers/agents.py
Normal file
314
backend/routers/agents.py
Normal file
@@ -0,0 +1,314 @@
|
||||
"""
|
||||
Agent管理路由
|
||||
"""
|
||||
from typing import List, Optional, Dict, Any
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from pydantic import BaseModel, Field
|
||||
from loguru import logger
|
||||
|
||||
from services.agent_service import AgentService, AGENT_TEMPLATES
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ============ 请求/响应模型 ============
|
||||
|
||||
class CapabilitiesModel(BaseModel):
|
||||
"""能力配置模型"""
|
||||
memory_enabled: bool = False
|
||||
mcp_tools: List[str] = []
|
||||
skills: List[str] = []
|
||||
multimodal: bool = False
|
||||
|
||||
|
||||
class BehaviorModel(BaseModel):
|
||||
"""行为配置模型"""
|
||||
speak_threshold: float = 0.5
|
||||
max_speak_per_round: int = 2
|
||||
speak_style: str = "balanced"
|
||||
|
||||
|
||||
class AgentCreateRequest(BaseModel):
|
||||
"""创建Agent请求"""
|
||||
name: str = Field(..., description="Agent名称")
|
||||
role: str = Field(..., description="角色定义")
|
||||
system_prompt: str = Field(..., description="系统提示词")
|
||||
provider_id: str = Field(..., description="使用的AI接口ID")
|
||||
temperature: float = Field(default=0.7, ge=0, le=2, description="温度参数")
|
||||
max_tokens: int = Field(default=2000, gt=0, description="最大token数")
|
||||
capabilities: Optional[CapabilitiesModel] = None
|
||||
behavior: Optional[BehaviorModel] = None
|
||||
avatar: Optional[str] = None
|
||||
color: str = "#1890ff"
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"name": "产品经理",
|
||||
"role": "产品规划和需求分析专家",
|
||||
"system_prompt": "你是一位经验丰富的产品经理...",
|
||||
"provider_id": "openrouter-abc123",
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 2000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AgentUpdateRequest(BaseModel):
|
||||
"""更新Agent请求"""
|
||||
name: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
system_prompt: Optional[str] = None
|
||||
provider_id: Optional[str] = None
|
||||
temperature: Optional[float] = Field(default=None, ge=0, le=2)
|
||||
max_tokens: Optional[int] = Field(default=None, gt=0)
|
||||
capabilities: Optional[CapabilitiesModel] = None
|
||||
behavior: Optional[BehaviorModel] = None
|
||||
avatar: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
enabled: Optional[bool] = None
|
||||
|
||||
|
||||
class AgentResponse(BaseModel):
|
||||
"""Agent响应"""
|
||||
agent_id: str
|
||||
name: str
|
||||
role: str
|
||||
system_prompt: str
|
||||
provider_id: str
|
||||
temperature: float
|
||||
max_tokens: int
|
||||
capabilities: Dict[str, Any]
|
||||
behavior: Dict[str, Any]
|
||||
avatar: Optional[str]
|
||||
color: str
|
||||
enabled: bool
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
class AgentTestRequest(BaseModel):
|
||||
"""Agent测试请求"""
|
||||
message: str = "你好,请简单介绍一下你自己。"
|
||||
|
||||
|
||||
class AgentTestResponse(BaseModel):
|
||||
"""Agent测试响应"""
|
||||
success: bool
|
||||
message: str
|
||||
response: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
tokens: Optional[int] = None
|
||||
latency_ms: Optional[float] = None
|
||||
|
||||
|
||||
class TemplateResponse(BaseModel):
|
||||
"""模板响应"""
|
||||
template_id: str
|
||||
name: str
|
||||
role: str
|
||||
system_prompt: str
|
||||
color: str
|
||||
|
||||
|
||||
class GeneratePromptRequest(BaseModel):
|
||||
"""生成提示词请求"""
|
||||
provider_id: str = Field(..., description="使用的AI接口ID")
|
||||
name: str = Field(..., description="Agent名称")
|
||||
role: str = Field(..., description="角色定位")
|
||||
description: Optional[str] = Field(None, description="额外描述(可选)")
|
||||
|
||||
|
||||
class GeneratePromptResponse(BaseModel):
|
||||
"""生成提示词响应"""
|
||||
success: bool
|
||||
message: Optional[str] = None
|
||||
prompt: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
tokens: Optional[int] = None
|
||||
|
||||
|
||||
# ============ 路由处理 ============
|
||||
|
||||
@router.post("", response_model=AgentResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_agent(request: AgentCreateRequest):
|
||||
"""
|
||||
创建新的Agent
|
||||
"""
|
||||
try:
|
||||
agent = await AgentService.create_agent(
|
||||
name=request.name,
|
||||
role=request.role,
|
||||
system_prompt=request.system_prompt,
|
||||
provider_id=request.provider_id,
|
||||
temperature=request.temperature,
|
||||
max_tokens=request.max_tokens,
|
||||
capabilities=request.capabilities.dict() if request.capabilities else None,
|
||||
behavior=request.behavior.dict() if request.behavior else None,
|
||||
avatar=request.avatar,
|
||||
color=request.color
|
||||
)
|
||||
|
||||
return _to_response(agent)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"创建Agent失败: {e}")
|
||||
raise HTTPException(status_code=500, detail="创建失败")
|
||||
|
||||
|
||||
@router.get("", response_model=List[AgentResponse])
|
||||
async def list_agents(enabled_only: bool = False):
|
||||
"""
|
||||
获取所有Agent
|
||||
"""
|
||||
agents = await AgentService.get_all_agents(enabled_only)
|
||||
return [_to_response(a) for a in agents]
|
||||
|
||||
|
||||
@router.get("/templates", response_model=List[TemplateResponse])
|
||||
async def list_templates():
|
||||
"""
|
||||
获取Agent预设模板
|
||||
"""
|
||||
return [
|
||||
TemplateResponse(
|
||||
template_id=tid,
|
||||
name=t["name"],
|
||||
role=t["role"],
|
||||
system_prompt=t["system_prompt"],
|
||||
color=t["color"]
|
||||
)
|
||||
for tid, t in AGENT_TEMPLATES.items()
|
||||
]
|
||||
|
||||
|
||||
@router.post("/generate-prompt", response_model=GeneratePromptResponse)
|
||||
async def generate_prompt(request: GeneratePromptRequest):
|
||||
"""
|
||||
使用AI生成Agent系统提示词
|
||||
"""
|
||||
result = await AgentService.generate_system_prompt(
|
||||
provider_id=request.provider_id,
|
||||
name=request.name,
|
||||
role=request.role,
|
||||
description=request.description
|
||||
)
|
||||
return GeneratePromptResponse(**result)
|
||||
|
||||
|
||||
@router.get("/{agent_id}", response_model=AgentResponse)
|
||||
async def get_agent(agent_id: str):
|
||||
"""
|
||||
获取指定Agent
|
||||
"""
|
||||
agent = await AgentService.get_agent(agent_id)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail="Agent不存在")
|
||||
return _to_response(agent)
|
||||
|
||||
|
||||
@router.put("/{agent_id}", response_model=AgentResponse)
|
||||
async def update_agent(agent_id: str, request: AgentUpdateRequest):
|
||||
"""
|
||||
更新Agent配置
|
||||
"""
|
||||
update_data = request.dict(exclude_unset=True)
|
||||
|
||||
# 转换嵌套模型
|
||||
if "capabilities" in update_data and update_data["capabilities"]:
|
||||
if hasattr(update_data["capabilities"], "dict"):
|
||||
update_data["capabilities"] = update_data["capabilities"].dict()
|
||||
if "behavior" in update_data and update_data["behavior"]:
|
||||
if hasattr(update_data["behavior"], "dict"):
|
||||
update_data["behavior"] = update_data["behavior"].dict()
|
||||
|
||||
try:
|
||||
agent = await AgentService.update_agent(agent_id, **update_data)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail="Agent不存在")
|
||||
return _to_response(agent)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.delete("/{agent_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_agent(agent_id: str):
|
||||
"""
|
||||
删除Agent
|
||||
"""
|
||||
success = await AgentService.delete_agent(agent_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Agent不存在")
|
||||
|
||||
|
||||
@router.post("/{agent_id}/test", response_model=AgentTestResponse)
|
||||
async def test_agent(agent_id: str, request: AgentTestRequest = None):
|
||||
"""
|
||||
测试Agent对话
|
||||
"""
|
||||
message = request.message if request else "你好,请简单介绍一下你自己。"
|
||||
result = await AgentService.test_agent(agent_id, message)
|
||||
return AgentTestResponse(**result)
|
||||
|
||||
|
||||
@router.post("/{agent_id}/duplicate", response_model=AgentResponse)
|
||||
async def duplicate_agent(agent_id: str, new_name: Optional[str] = None):
|
||||
"""
|
||||
复制Agent
|
||||
"""
|
||||
agent = await AgentService.duplicate_agent(agent_id, new_name)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail="源Agent不存在")
|
||||
return _to_response(agent)
|
||||
|
||||
|
||||
@router.post("/from-template/{template_id}", response_model=AgentResponse)
|
||||
async def create_from_template(template_id: str, provider_id: str):
|
||||
"""
|
||||
从模板创建Agent
|
||||
"""
|
||||
if template_id not in AGENT_TEMPLATES:
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
|
||||
template = AGENT_TEMPLATES[template_id]
|
||||
|
||||
try:
|
||||
agent = await AgentService.create_agent(
|
||||
name=template["name"],
|
||||
role=template["role"],
|
||||
system_prompt=template["system_prompt"],
|
||||
provider_id=provider_id,
|
||||
color=template["color"]
|
||||
)
|
||||
return _to_response(agent)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
# ============ 辅助函数 ============
|
||||
|
||||
def _to_response(agent) -> AgentResponse:
|
||||
"""
|
||||
转换为响应模型
|
||||
"""
|
||||
return AgentResponse(
|
||||
agent_id=agent.agent_id,
|
||||
name=agent.name,
|
||||
role=agent.role,
|
||||
system_prompt=agent.system_prompt,
|
||||
provider_id=agent.provider_id,
|
||||
temperature=agent.temperature,
|
||||
max_tokens=agent.max_tokens,
|
||||
capabilities=agent.capabilities,
|
||||
behavior=agent.behavior,
|
||||
avatar=agent.avatar,
|
||||
color=agent.color,
|
||||
enabled=agent.enabled,
|
||||
created_at=agent.created_at.isoformat(),
|
||||
updated_at=agent.updated_at.isoformat()
|
||||
)
|
||||
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()
|
||||
)
|
||||
136
backend/routers/discussions.py
Normal file
136
backend/routers/discussions.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
讨论结果路由
|
||||
"""
|
||||
from typing import List, Optional, Dict, Any
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from models.discussion_result import DiscussionResult
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ============ 响应模型 ============
|
||||
|
||||
class DiscussionResponse(BaseModel):
|
||||
"""讨论结果响应"""
|
||||
discussion_id: str
|
||||
room_id: str
|
||||
objective: str
|
||||
consensus_reached: bool
|
||||
confidence: float
|
||||
summary: str
|
||||
action_items: List[str]
|
||||
unresolved_issues: List[str]
|
||||
key_decisions: List[str]
|
||||
total_rounds: int
|
||||
total_messages: int
|
||||
participating_agents: List[str]
|
||||
agent_contributions: Dict[str, int]
|
||||
status: str
|
||||
end_reason: str
|
||||
created_at: str
|
||||
completed_at: Optional[str]
|
||||
|
||||
|
||||
class DiscussionListResponse(BaseModel):
|
||||
"""讨论列表响应"""
|
||||
discussions: List[DiscussionResponse]
|
||||
total: int
|
||||
|
||||
|
||||
# ============ 路由处理 ============
|
||||
|
||||
@router.get("", response_model=DiscussionListResponse)
|
||||
async def list_discussions(
|
||||
room_id: Optional[str] = None,
|
||||
limit: int = 20,
|
||||
skip: int = 0
|
||||
):
|
||||
"""
|
||||
获取讨论结果列表
|
||||
"""
|
||||
query = {}
|
||||
if room_id:
|
||||
query["room_id"] = room_id
|
||||
|
||||
discussions = await DiscussionResult.find(query).sort(
|
||||
"-created_at"
|
||||
).skip(skip).limit(limit).to_list()
|
||||
|
||||
total = await DiscussionResult.find(query).count()
|
||||
|
||||
return DiscussionListResponse(
|
||||
discussions=[_to_response(d) for d in discussions],
|
||||
total=total
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{discussion_id}", response_model=DiscussionResponse)
|
||||
async def get_discussion(discussion_id: str):
|
||||
"""
|
||||
获取指定讨论结果
|
||||
"""
|
||||
discussion = await DiscussionResult.find_one(
|
||||
DiscussionResult.discussion_id == discussion_id
|
||||
)
|
||||
|
||||
if not discussion:
|
||||
raise HTTPException(status_code=404, detail="讨论记录不存在")
|
||||
|
||||
return _to_response(discussion)
|
||||
|
||||
|
||||
@router.get("/room/{room_id}", response_model=List[DiscussionResponse])
|
||||
async def get_room_discussions(room_id: str, limit: int = 10):
|
||||
"""
|
||||
获取聊天室的讨论历史
|
||||
"""
|
||||
discussions = await DiscussionResult.find(
|
||||
{"room_id": room_id}
|
||||
).sort("-created_at").limit(limit).to_list()
|
||||
|
||||
return [_to_response(d) for d in discussions]
|
||||
|
||||
|
||||
@router.get("/room/{room_id}/latest", response_model=DiscussionResponse)
|
||||
async def get_latest_discussion(room_id: str):
|
||||
"""
|
||||
获取聊天室最新的讨论结果
|
||||
"""
|
||||
discussion = await DiscussionResult.find(
|
||||
{"room_id": room_id}
|
||||
).sort("-created_at").first_or_none()
|
||||
|
||||
if not discussion:
|
||||
raise HTTPException(status_code=404, detail="没有找到讨论记录")
|
||||
|
||||
return _to_response(discussion)
|
||||
|
||||
|
||||
# ============ 辅助函数 ============
|
||||
|
||||
def _to_response(discussion: DiscussionResult) -> DiscussionResponse:
|
||||
"""
|
||||
转换为响应模型
|
||||
"""
|
||||
return DiscussionResponse(
|
||||
discussion_id=discussion.discussion_id,
|
||||
room_id=discussion.room_id,
|
||||
objective=discussion.objective,
|
||||
consensus_reached=discussion.consensus_reached,
|
||||
confidence=discussion.confidence,
|
||||
summary=discussion.summary,
|
||||
action_items=discussion.action_items,
|
||||
unresolved_issues=discussion.unresolved_issues,
|
||||
key_decisions=discussion.key_decisions,
|
||||
total_rounds=discussion.total_rounds,
|
||||
total_messages=discussion.total_messages,
|
||||
participating_agents=discussion.participating_agents,
|
||||
agent_contributions=discussion.agent_contributions,
|
||||
status=discussion.status,
|
||||
end_reason=discussion.end_reason,
|
||||
created_at=discussion.created_at.isoformat(),
|
||||
completed_at=discussion.completed_at.isoformat() if discussion.completed_at else None
|
||||
)
|
||||
241
backend/routers/providers.py
Normal file
241
backend/routers/providers.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""
|
||||
AI接口管理路由
|
||||
"""
|
||||
from typing import List, Optional, Dict, Any
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from pydantic import BaseModel, Field
|
||||
from loguru import logger
|
||||
|
||||
from services.ai_provider_service import AIProviderService
|
||||
from utils.encryption import mask_api_key
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ============ 请求/响应模型 ============
|
||||
|
||||
class ProxyConfigModel(BaseModel):
|
||||
"""代理配置模型"""
|
||||
http_proxy: Optional[str] = None
|
||||
https_proxy: Optional[str] = None
|
||||
no_proxy: List[str] = []
|
||||
|
||||
|
||||
class RateLimitModel(BaseModel):
|
||||
"""速率限制模型"""
|
||||
requests_per_minute: int = 60
|
||||
tokens_per_minute: int = 100000
|
||||
|
||||
|
||||
class ProviderCreateRequest(BaseModel):
|
||||
"""创建AI接口请求"""
|
||||
provider_type: str = Field(..., description="提供商类型: minimax, zhipu, openrouter, kimi, deepseek, gemini, ollama, llmstudio")
|
||||
name: str = Field(..., description="自定义名称")
|
||||
model: str = Field(..., description="模型名称")
|
||||
api_key: str = Field(default="", description="API密钥")
|
||||
base_url: str = Field(default="", description="API基础URL")
|
||||
use_proxy: bool = Field(default=False, description="是否使用代理")
|
||||
proxy_config: Optional[ProxyConfigModel] = None
|
||||
rate_limit: Optional[RateLimitModel] = None
|
||||
timeout: int = Field(default=60, description="超时时间(秒)")
|
||||
extra_params: Dict[str, Any] = Field(default_factory=dict, description="额外参数")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"provider_type": "openrouter",
|
||||
"name": "OpenRouter GPT-4",
|
||||
"model": "openai/gpt-4-turbo",
|
||||
"api_key": "sk-xxx",
|
||||
"use_proxy": True,
|
||||
"proxy_config": {
|
||||
"http_proxy": "http://127.0.0.1:7890",
|
||||
"https_proxy": "http://127.0.0.1:7890"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ProviderUpdateRequest(BaseModel):
|
||||
"""更新AI接口请求"""
|
||||
name: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
api_key: Optional[str] = None
|
||||
base_url: Optional[str] = None
|
||||
use_proxy: Optional[bool] = None
|
||||
proxy_config: Optional[ProxyConfigModel] = None
|
||||
rate_limit: Optional[RateLimitModel] = None
|
||||
timeout: Optional[int] = None
|
||||
extra_params: Optional[Dict[str, Any]] = None
|
||||
enabled: Optional[bool] = None
|
||||
|
||||
|
||||
class ProviderResponse(BaseModel):
|
||||
"""AI接口响应"""
|
||||
provider_id: str
|
||||
provider_type: str
|
||||
name: str
|
||||
api_key_masked: str
|
||||
base_url: str
|
||||
model: str
|
||||
use_proxy: bool
|
||||
proxy_config: Dict[str, Any]
|
||||
rate_limit: Dict[str, int]
|
||||
timeout: int
|
||||
extra_params: Dict[str, Any]
|
||||
enabled: bool
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
class TestConfigRequest(BaseModel):
|
||||
"""测试配置请求"""
|
||||
provider_type: str
|
||||
api_key: str
|
||||
base_url: str = ""
|
||||
model: str = ""
|
||||
use_proxy: bool = False
|
||||
proxy_config: Optional[ProxyConfigModel] = None
|
||||
timeout: int = 30
|
||||
|
||||
|
||||
class TestResponse(BaseModel):
|
||||
"""测试响应"""
|
||||
success: bool
|
||||
message: str
|
||||
model: Optional[str] = None
|
||||
latency_ms: Optional[float] = None
|
||||
|
||||
|
||||
# ============ 路由处理 ============
|
||||
|
||||
@router.post("", response_model=ProviderResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_provider(request: ProviderCreateRequest):
|
||||
"""
|
||||
创建新的AI接口配置
|
||||
"""
|
||||
try:
|
||||
provider = await AIProviderService.create_provider(
|
||||
provider_type=request.provider_type,
|
||||
name=request.name,
|
||||
model=request.model,
|
||||
api_key=request.api_key,
|
||||
base_url=request.base_url,
|
||||
use_proxy=request.use_proxy,
|
||||
proxy_config=request.proxy_config.dict() if request.proxy_config else None,
|
||||
rate_limit=request.rate_limit.dict() if request.rate_limit else None,
|
||||
timeout=request.timeout,
|
||||
extra_params=request.extra_params
|
||||
)
|
||||
|
||||
return _to_response(provider)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"创建AI接口失败: {e}")
|
||||
raise HTTPException(status_code=500, detail="创建失败")
|
||||
|
||||
|
||||
@router.get("", response_model=List[ProviderResponse])
|
||||
async def list_providers(enabled_only: bool = False):
|
||||
"""
|
||||
获取所有AI接口配置
|
||||
"""
|
||||
providers = await AIProviderService.get_all_providers(enabled_only)
|
||||
return [_to_response(p) for p in providers]
|
||||
|
||||
|
||||
@router.get("/{provider_id}", response_model=ProviderResponse)
|
||||
async def get_provider(provider_id: str):
|
||||
"""
|
||||
获取指定AI接口配置
|
||||
"""
|
||||
provider = await AIProviderService.get_provider(provider_id)
|
||||
if not provider:
|
||||
raise HTTPException(status_code=404, detail="AI接口不存在")
|
||||
return _to_response(provider)
|
||||
|
||||
|
||||
@router.put("/{provider_id}", response_model=ProviderResponse)
|
||||
async def update_provider(provider_id: str, request: ProviderUpdateRequest):
|
||||
"""
|
||||
更新AI接口配置
|
||||
"""
|
||||
update_data = request.dict(exclude_unset=True)
|
||||
|
||||
# 转换嵌套模型
|
||||
if "proxy_config" in update_data and update_data["proxy_config"]:
|
||||
update_data["proxy_config"] = update_data["proxy_config"].dict() if hasattr(update_data["proxy_config"], "dict") else update_data["proxy_config"]
|
||||
if "rate_limit" in update_data and update_data["rate_limit"]:
|
||||
update_data["rate_limit"] = update_data["rate_limit"].dict() if hasattr(update_data["rate_limit"], "dict") else update_data["rate_limit"]
|
||||
|
||||
try:
|
||||
provider = await AIProviderService.update_provider(provider_id, **update_data)
|
||||
if not provider:
|
||||
raise HTTPException(status_code=404, detail="AI接口不存在")
|
||||
return _to_response(provider)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.delete("/{provider_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_provider(provider_id: str):
|
||||
"""
|
||||
删除AI接口配置
|
||||
"""
|
||||
success = await AIProviderService.delete_provider(provider_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="AI接口不存在")
|
||||
|
||||
|
||||
@router.post("/{provider_id}/test", response_model=TestResponse)
|
||||
async def test_provider(provider_id: str):
|
||||
"""
|
||||
测试AI接口连接
|
||||
"""
|
||||
result = await AIProviderService.test_provider(provider_id)
|
||||
return TestResponse(**result)
|
||||
|
||||
|
||||
@router.post("/test", response_model=TestResponse)
|
||||
async def test_provider_config(request: TestConfigRequest):
|
||||
"""
|
||||
测试AI接口配置(不保存)
|
||||
"""
|
||||
result = await AIProviderService.test_provider_config(
|
||||
provider_type=request.provider_type,
|
||||
api_key=request.api_key,
|
||||
base_url=request.base_url,
|
||||
model=request.model,
|
||||
use_proxy=request.use_proxy,
|
||||
proxy_config=request.proxy_config.dict() if request.proxy_config else None,
|
||||
timeout=request.timeout
|
||||
)
|
||||
return TestResponse(**result)
|
||||
|
||||
|
||||
# ============ 辅助函数 ============
|
||||
|
||||
def _to_response(provider) -> ProviderResponse:
|
||||
"""
|
||||
转换为响应模型
|
||||
"""
|
||||
return ProviderResponse(
|
||||
provider_id=provider.provider_id,
|
||||
provider_type=provider.provider_type,
|
||||
name=provider.name,
|
||||
api_key_masked=mask_api_key(provider.api_key) if provider.api_key else "",
|
||||
base_url=provider.base_url,
|
||||
model=provider.model,
|
||||
use_proxy=provider.use_proxy,
|
||||
proxy_config=provider.proxy_config,
|
||||
rate_limit=provider.rate_limit,
|
||||
timeout=provider.timeout,
|
||||
extra_params=provider.extra_params,
|
||||
enabled=provider.enabled,
|
||||
created_at=provider.created_at.isoformat(),
|
||||
updated_at=provider.updated_at.isoformat()
|
||||
)
|
||||
Reference in New Issue
Block a user