fix: delete agent 500 error + dynamic personality + deployment guide

- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions,
  schedules, executions, team_members) and unbind goals/tasks before delete
- Remove hardcoded personality templates in Android, replace with dynamic
  system prompt generation from name + description
- Set promptSectionsEnabled=false to bypass PromptComposer for personality
- Add Tencent Cloud Linux deployment guide (Docker Compose)
- Accumulated backend service updates, frontend UI fixes, Android app changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 01:17:21 +08:00
parent 86b98865e3
commit beff3fac8d
1084 changed files with 117315 additions and 1281 deletions

View File

@@ -1,8 +1,9 @@
"""
Agent管理API
"""
from fastapi import APIRouter, Depends, HTTPException, status, Query, Response
from fastapi import APIRouter, Depends, HTTPException, status, Query, Response, Request
from sqlalchemy.orm import Session
from sqlalchemy import text
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any, Union
from datetime import datetime
@@ -61,16 +62,18 @@ class SceneTemplateItem(BaseModel):
category: Optional[str] = None
default_temperature: Optional[float] = None
parameter_hints: List[str] = Field(default_factory=list)
contract_id: Optional[str] = None
class AgentFromSceneTemplateCreate(BaseModel):
"""从场景模板创建 Agent"""
"""从场景模板创建 Agent(支持 DSL 契约参数)"""
template_id: str
name: str
description: Optional[str] = None
parameters: Dict[str, Any] = Field(default_factory=dict)
budget_config: Optional[Dict[str, Any]] = None
contract_id: Optional[str] = None
class PreviewChatTurnResponse(BaseModel):
@@ -96,6 +99,9 @@ class AgentResponse(BaseModel):
version: int
status: str
user_id: Optional[str] # 允许为None
parent_agent_id: Optional[str] = None
agent_type: Optional[str] = None
category: Optional[str] = None
created_at: datetime
updated_at: datetime
@@ -110,13 +116,14 @@ async def get_agents(
limit: int = Query(100, ge=1, le=100, description="每页记录数"),
search: Optional[str] = Query(None, description="搜索关键词(按名称或描述)"),
status: Optional[str] = Query(None, description="状态筛选"),
workspace_id: Optional[str] = Query(None, description="工作区ID筛选"),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
获取Agent列表
支持分页、搜索、状态筛选
支持分页、搜索、状态筛选、工作区筛选
"""
# 管理员可以看到所有Agent普通用户只能看到自己拥有的或有read权限的
if current_user.role == "admin":
@@ -125,10 +132,10 @@ async def get_agents(
# 获取用户拥有或有read权限的Agent
from sqlalchemy import or_
from app.models.permission import AgentPermission
# 用户拥有的Agent
owned_agents = db.query(Agent.id).filter(Agent.user_id == current_user.id).subquery()
# 用户有read权限的Agent通过用户ID或角色
user_permissions = db.query(AgentPermission.agent_id).filter(
AgentPermission.permission_type == "read",
@@ -137,14 +144,18 @@ async def get_agents(
AgentPermission.role_id.in_([r.id for r in current_user.roles])
)
).subquery()
query = db.query(Agent).filter(
or_(
Agent.id.in_(db.query(owned_agents.c.id)),
Agent.id.in_(db.query(user_permissions.c.agent_id))
)
)
# 工作区筛选
if workspace_id:
query = query.filter(Agent.workspace_id == workspace_id)
# 搜索:按名称或描述搜索
if search:
search_pattern = f"%{search}%"
@@ -187,26 +198,36 @@ async def get_agents(
@router.post("", response_model=AgentResponse, status_code=status.HTTP_201_CREATED)
async def create_agent(
agent_data: AgentCreate,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
创建Agent
创建时会验证工作流配置的有效性
"""
# 从 JWT 提取当前工作区 ID
from app.core.security import decode_access_token
ws_id = None
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer "):
payload = decode_access_token(auth_header[7:])
if payload:
ws_id = payload.get("ws") or None
# 验证工作流配置
if "nodes" not in agent_data.workflow_config or "edges" not in agent_data.workflow_config:
raise ValidationError("工作流配置必须包含nodes和edges")
nodes = agent_data.workflow_config.get("nodes", [])
edges = agent_data.workflow_config.get("edges", [])
# 验证工作流
validation_result = validate_workflow(nodes, edges)
if not validation_result["valid"]:
raise ValidationError(f"工作流配置验证失败: {', '.join(validation_result['errors'])}")
# 检查名称是否重复
existing_agent = db.query(Agent).filter(
Agent.name == agent_data.name,
@@ -214,7 +235,7 @@ async def create_agent(
).first()
if existing_agent:
raise ConflictError(f"Agent名称 '{agent_data.name}' 已存在")
# 创建Agent
agent = Agent(
name=agent_data.name,
@@ -222,6 +243,7 @@ async def create_agent(
workflow_config=agent_data.workflow_config,
budget_config=agent_data.budget_config,
user_id=current_user.id,
workspace_id=ws_id,
status="draft",
category=agent_data.category,
tags=agent_data.tags,
@@ -377,9 +399,33 @@ async def delete_agent(
raise HTTPException(status_code=403, detail="无权删除此Agent")
agent_name = agent.name
# 1. 清理直接关联的记录(级联删除)
related_tables = [
"agent_llm_logs",
"agent_permissions",
"agent_schedules",
"executions",
"team_members",
]
for table in related_tables:
db.execute(text(f"DELETE FROM {table} WHERE agent_id = :aid"), {"aid": agent_id})
logger.debug(f"已清理 {table} 中 agent_id={agent_id} 的记录")
# 2. 解除分配关系(设为 NULL
db.execute(
text("UPDATE goals SET main_agent_id = NULL WHERE main_agent_id = :aid"),
{"aid": agent_id},
)
db.execute(
text("UPDATE tasks SET assigned_agent_id = NULL WHERE assigned_agent_id = :aid"),
{"aid": agent_id},
)
# 3. 删除 Agent 自身
db.delete(agent)
db.commit()
logger.info(f"用户 {current_user.username} 删除了Agent: {agent_name} ({agent_id})")
return {"message": "Agent已删除"}
@@ -773,3 +819,67 @@ async def import_agent(
db.commit()
db.refresh(agent)
return agent
# ——— Agent 间知识共享 API ———
class InheritKnowledgeResponse(BaseModel):
child_agent_id: str
parent_agent_id: str
knowledge_entries_copied: int
global_knowledge_shared: int
learning_patterns_copied: int
message: str
@router.post("/{agent_id}/inherit-knowledge", response_model=InheritKnowledgeResponse)
def inherit_agent_knowledge(
agent_id: str,
parent_agent_id: str = Query(..., description="父 Agent ID"),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""让子 Agent 从父 Agent 继承知识条目、全局知识和学习模式。"""
child = db.query(Agent).filter(Agent.id == agent_id).first()
if not child:
raise NotFoundError("Agent", agent_id)
parent = db.query(Agent).filter(Agent.id == parent_agent_id).first()
if not parent:
raise NotFoundError("父 Agent", parent_agent_id)
# 记录父子关系
child.parent_agent_id = parent_agent_id
db.commit()
try:
from app.services.knowledge_sharing import inherit_knowledge_from_parent
result = inherit_knowledge_from_parent(agent_id, parent_agent_id, db)
result["message"] = f"已从父 Agent 继承 {result['knowledge_entries_copied']} 条知识、" \
f"{result['global_knowledge_shared']} 条全局知识、" \
f"{result['learning_patterns_copied']} 条学习模式"
return InheritKnowledgeResponse(**result)
except Exception as e:
raise HTTPException(status_code=500, detail=f"知识继承失败: {e}")
@router.get("/{agent_id}/parent-knowledge")
def get_parent_knowledge_preview(
agent_id: str,
limit: int = Query(5, ge=1, le=20),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""预览 Agent 可用的父级知识(不实际继承)。"""
agent = db.query(Agent).filter(Agent.id == agent_id).first()
if not agent:
raise NotFoundError("Agent", agent_id)
from app.services.knowledge_sharing import get_parent_knowledge_context
context = get_parent_knowledge_context(agent_id, max_entries=limit, db=db)
return {
"agent_id": agent_id,
"parent_agent_id": getattr(agent, "parent_agent_id", None),
"has_parent_knowledge": bool(context),
"context_preview": context[:2000] if context else "",
}