feat: 完成登录页面基础结构

This commit is contained in:
2025-12-24 02:04:44 +00:00
parent 9b5f60fa91
commit a4b19d21d6
3 changed files with 151 additions and 68 deletions
+104 -51
View File
@@ -11,6 +11,7 @@ from datetime import datetime
from typing import Any, Dict, List, Optional
from fastapi import Query
from uuid import UUID
import uuid
import structlog
import time
@@ -219,9 +220,9 @@ def _build_agent_card(agent: Agent) -> AgentCard:
tools=agent.tools or [],
capabilities=agent.capabilities or [],
endpoints={
"mcp": f"mcp://localhost:8002/agents/{agent.id}",
"http": f"http://localhost:8002/agents/{agent.id}",
"websocket": f"ws://localhost:8002/agents/{agent.id}/ws"
"mcp": f"mcp://localhost:8002/agents/{str(agent.id)}",
"http": f"http://localhost:8002/agents/{str(agent.id)}",
"websocket": f"ws://localhost:8002/agents/{str(agent.id)}/ws"
},
status=agent.status,
version=agent.version,
@@ -363,6 +364,9 @@ async def health_check():
services=health_data["services"]
)
# 固定的测试用户UUID
TEST_USER_ID = uuid.UUID("00000000-0000-0000-0000-000000000001")
@app.post("/agents", response_model=AgentCard)
async def create_agent(
request: AgentCreateRequest,
@@ -370,23 +374,24 @@ async def create_agent(
):
"""创建新的Agent"""
try:
# 确保有可用的Owner
owner_id = request.owner_id
if owner_id is None:
result = await db.execute(select(User.id).order_by(User.created_at).limit(1))
owner_id = result.scalar()
if owner_id is None:
default_user = User(
username="system",
email="system@taiji-ai.com",
hashed_password="",
full_name="System",
is_active=True,
is_admin=True
)
db.add(default_user)
await db.flush()
owner_id = default_user.id
# 使用固定的测试用户ID
owner_id = TEST_USER_ID
# 确保测试用户存在(如果不存在则创建)
result = await db.execute(select(User).where(User.id == TEST_USER_ID))
test_user = result.scalar_one_or_none()
if test_user is None:
test_user = User(
id=TEST_USER_ID,
username="test_user",
email="test@taiji-ai.com",
hashed_password="", # 测试用户不需要密码
full_name="测试用户",
is_active=True,
is_admin=False
)
db.add(test_user)
await db.flush()
# 创建Agent记录
agent = Agent(
@@ -396,35 +401,60 @@ async def create_agent(
goal=request.goal,
tools=request.tools,
config=request.config,
owner_id=owner_id
capabilities=request.capabilities,
owner_id=owner_id # 使用固定的测试用户ID
)
db.add(agent)
await db.commit()
await db.refresh(agent)
# 生成Agent Card
agent_card = AgentCard(
id=agent.id,
name=agent.name,
description=agent.description,
role=agent.role,
goal=agent.goal,
tools=agent.tools,
endpoints={
"mcp": f"mcp://localhost:8002/agents/{agent.id}",
"http": f"http://localhost:8002/agents/{agent.id}",
"websocket": f"ws://localhost:8002/agents/{agent.id}/ws"
# 确保所有UUID对象都是标准类型(转换asyncpg的UUID)
def convert_uuid_to_standard(uuid_obj):
"""将asyncpg的UUID转换为标准UUID"""
if uuid_obj is None:
return None
try:
from asyncpg.pgproto.pgproto import UUID as AsyncUUID
if isinstance(uuid_obj, AsyncUUID):
return uuid.UUID(str(uuid_obj))
except (ImportError, AttributeError):
pass
if isinstance(uuid_obj, uuid.UUID):
return uuid_obj
try:
return uuid.UUID(str(uuid_obj))
except (ValueError, TypeError):
return uuid_obj
# 转换agent.id为字符串,然后让Pydantic处理
agent_id_str = str(agent.id)
agent_id = uuid.UUID(agent_id_str)
# 生成Agent Card - 使用字典方式创建,避免Pydantic验证时的问题
agent_card_dict = {
"id": agent_id,
"name": agent.name,
"description": agent.description,
"role": agent.role,
"goal": agent.goal,
"tools": agent.tools or [],
"capabilities": agent.capabilities or [],
"endpoints": {
"mcp": f"mcp://localhost:8002/agents/{agent_id_str}",
"http": f"http://localhost:8002/agents/{agent_id_str}",
"websocket": f"ws://localhost:8002/agents/{agent_id_str}/ws"
},
created_at=agent.created_at,
updated_at=agent.updated_at
)
"created_at": agent.created_at,
"updated_at": agent.updated_at
}
agent_card = AgentCard(**agent_card_dict)
# 缓存到Redis
if redis_client:
cached_agent = agent_card.model_dump(mode="json")
await redis_client.setex(
f"agent:{agent.id}",
f"agent:{str(agent.id)}",
3600, # 1小时过期
json.dumps(cached_agent, ensure_ascii=False)
)
@@ -449,7 +479,9 @@ async def create_agent(
except Exception as e:
# 记录失败指标
agents_registered_total.labels(status="error").inc()
import traceback
logger.error(f"创建Agent失败: {e}")
logger.error(f"错误堆栈:\n{traceback.format_exc()}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/agents", response_model=List[AgentCard])
@@ -611,16 +643,37 @@ async def list_tools(db: AsyncSession = Depends(get_db)):
logger.error(f"获取工具列表失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.websocket("/agents/{agent_id}/ws")
async def websocket_endpoint(websocket: WebSocket, agent_id: str):
"""Agent WebSocket连接端点"""
await websocket.accept()
active_websockets[agent_id] = websocket
websocket_connections_total.labels(status="connected").inc()
websocket_connections_active.inc()
@app.websocket("/ws/{agent_name_or_id}")
async def websocket_endpoint_by_name(websocket: WebSocket, agent_name_or_id: str, db: AsyncSession = Depends(get_db)):
"""Agent WebSocket连接端点(通过名称或ID)"""
agent_id = None
try:
logger.info(f"WebSocket连接建立: {agent_id}")
# 尝试通过名称或ID查找Agent
agent = None
try:
# 尝试作为UUID解析
agent_uuid = uuid.UUID(agent_name_or_id)
result = await db.execute(select(Agent).where(Agent.id == agent_uuid))
agent = result.scalar_one_or_none()
except ValueError:
# 如果不是UUID,则作为名称查找
result = await db.execute(select(Agent).where(Agent.name == agent_name_or_id))
agent = result.scalar_one_or_none()
if agent is None:
await websocket.close(code=1008, reason=f"Agent not found: {agent_name_or_id}")
websocket_connections_total.labels(status="rejected").inc()
logger.warning(f"WebSocket连接被拒绝: Agent不存在 - {agent_name_or_id}")
return
# 接受连接
await websocket.accept()
agent_id = str(agent.id)
active_websockets[agent_id] = websocket
websocket_connections_total.labels(status="connected").inc()
websocket_connections_active.inc()
logger.info(f"WebSocket连接建立: {agent.name} ({agent_id})")
while True:
# 等待客户端消息
@@ -663,11 +716,11 @@ async def websocket_endpoint(websocket: WebSocket, agent_id: str):
mcp_request_duration.labels(method=request.method).observe(duration)
raise
await websocket.send_json({
"type": "mcp_response",
"payload": result.dict()
})
websocket_messages_total.labels(direction="outbound").inc()
await websocket.send_json({
"type": "mcp_response",
"payload": result.model_dump(mode="json")
})
websocket_messages_total.labels(direction="outbound").inc()
except WebSocketDisconnect:
websocket_connections_total.labels(status="disconnected").inc()
+15
View File
@@ -38,6 +38,21 @@ class GUID(TypeDecorator):
def process_result_value(self, value, dialect):
if value is None:
return value
# 如果已经是UUID对象(包括asyncpg的UUID),先转换为字符串再转换
if hasattr(value, '__str__') and not isinstance(value, str):
# 处理asyncpg的UUID对象
try:
from asyncpg.pgproto.pgproto import UUID as AsyncUUID
if isinstance(value, AsyncUUID):
value = str(value)
except (ImportError, AttributeError):
pass
# 如果是标准UUID对象,直接返回
if isinstance(value, uuid.UUID):
return value
# 其他对象转换为字符串
value = str(value)
# 字符串转换为UUID
return uuid.UUID(value)
+32 -17
View File
@@ -16,6 +16,24 @@ class BaseSchema(BaseModel):
datetime: lambda v: v.isoformat(),
uuid.UUID: lambda v: str(v),
}
@staticmethod
def _convert_uuid(value):
"""转换asyncpg的UUID为标准UUID"""
if value is None:
return None
try:
from asyncpg.pgproto.pgproto import UUID as AsyncUUID
if isinstance(value, AsyncUUID):
return uuid.UUID(str(value))
except (ImportError, AttributeError):
pass
if isinstance(value, uuid.UUID):
return value
try:
return uuid.UUID(str(value))
except (ValueError, TypeError):
return value
# ========== MCP协议相关 ==========
@@ -94,30 +112,17 @@ class ToolResult(BaseSchema):
class AgentCreateRequest(BaseModel):
"""创建Agent请求"""
name: str = Field(..., min_length=1, max_length=100)
name: str
description: Optional[str] = None
role: str = Field(
default="general-purpose agent",
min_length=1,
max_length=200
)
goal: str = Field(
default="Handle generic MCP tasks and routing",
min_length=1
)
role: str = "general-purpose agent"
goal: str = "Handle generic MCP tasks and routing"
tools: List[str] = [] # 工具名称列表
config: Dict[str, Any] = {}
capabilities: List[str] = []
owner_id: Optional[uuid.UUID] = None
owner_id: Optional[Union[uuid.UUID, str]] = None
@validator('name')
def validate_name(cls, v):
"""验证Agent名称"""
if not v.replace('-', '').replace('_', '').isalnum():
raise ValueError('名称只能包含字母、数字、连字符和下划线')
return v
class AgentUpdateRequest(BaseModel):
@@ -157,6 +162,16 @@ class AgentCard(BaseSchema):
# 时间信息
created_at: datetime
updated_at: datetime
@validator('id', pre=True)
def validate_id(cls, v):
"""转换id为标准UUID"""
if v is None:
return None
# 先转换为字符串,避免asyncpg UUID对象的问题
v_str = str(v)
# 然后转换为标准UUID
return uuid.UUID(v_str)
class AgentExecution(BaseModel):