forked from xiaohei/taiji-AI-PAD
feat: 完成登录页面基础结构
This commit is contained in:
+104
-51
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user