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:
Claude Code
2026-02-03 19:20:02 +08:00
commit edbddf855d
76 changed files with 14681 additions and 0 deletions

View 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
View 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()
)

View 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()
)

View 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
)

View 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()
)