2026-03-24 15:07:19 +08:00
|
|
|
from sqlalchemy import or_, select
|
|
|
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
2026-03-29 20:31:13 +08:00
|
|
|
from app.models.skill import Skill
|
2026-03-24 15:07:19 +08:00
|
|
|
from app.models.user import User
|
|
|
|
|
from app.services.auth_service import get_password_hash
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 20:31:13 +08:00
|
|
|
BUILTIN_SKILLS = [
|
|
|
|
|
{
|
|
|
|
|
'name': '今日重点拆解',
|
|
|
|
|
'description': '帮助日程规划师从上下文中提炼今天最值得推进的事项。',
|
|
|
|
|
'instructions': '优先识别今天最关键的 1-3 个重点,说明原因,并给出可执行顺序。',
|
|
|
|
|
'agent_type': 'schedule_planner',
|
|
|
|
|
'tools': ['calendar', 'tasks'],
|
|
|
|
|
'visibility': 'market',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': '周计划编排',
|
|
|
|
|
'description': '把本周目标整理成可落地的节奏与时间块。',
|
|
|
|
|
'instructions': '将目标拆成周内节奏安排,明确先后顺序、时间块与缓冲。',
|
|
|
|
|
'agent_type': 'schedule_planner',
|
|
|
|
|
'tools': ['calendar'],
|
|
|
|
|
'visibility': 'market',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': '时间冲突分析',
|
|
|
|
|
'description': '识别任务、日程与优先级之间的冲突。',
|
|
|
|
|
'instructions': '分析冲突来源、影响和推荐取舍,必要时给出替代方案。',
|
|
|
|
|
'agent_type': 'schedule_planner',
|
|
|
|
|
'tools': ['calendar', 'tasks'],
|
|
|
|
|
'visibility': 'market',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': '任务执行 SOP',
|
|
|
|
|
'description': '为执行角色提供标准执行步骤和结果回报格式。',
|
|
|
|
|
'instructions': '执行前先确认目标与边界,执行中记录关键动作,执行后输出结果、风险与下一步。',
|
|
|
|
|
'agent_type': 'executor',
|
|
|
|
|
'tools': ['shell', 'api_calls'],
|
|
|
|
|
'visibility': 'market',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': '外部交互推进',
|
|
|
|
|
'description': '支持论坛、外部接口或内容发布类动作。',
|
|
|
|
|
'instructions': '围绕外部交互任务,优先保证动作完整、结果清晰、反馈及时。',
|
|
|
|
|
'agent_type': 'executor',
|
|
|
|
|
'tools': ['api_calls', 'git'],
|
|
|
|
|
'visibility': 'market',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': '知识检索摘要',
|
|
|
|
|
'description': '从知识中枢中提炼与当前问题最相关的信息。',
|
|
|
|
|
'instructions': '检索后只保留当前决策需要的内容,输出摘要、来源与缺口。',
|
|
|
|
|
'agent_type': 'librarian',
|
|
|
|
|
'tools': ['web_search', 'database'],
|
|
|
|
|
'visibility': 'market',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': '图谱沉淀策略',
|
|
|
|
|
'description': '帮助知识管理员把零散信息沉淀为结构化关系。',
|
|
|
|
|
'instructions': '识别应沉淀的实体、关系与后续可检索维度。',
|
|
|
|
|
'agent_type': 'librarian',
|
|
|
|
|
'tools': ['database'],
|
|
|
|
|
'visibility': 'market',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': '风险识别模板',
|
|
|
|
|
'description': '帮助分析师快速识别当前推进中的风险点。',
|
|
|
|
|
'instructions': '从进度、依赖、资源与外部信号中提炼风险,并按严重度排序。',
|
|
|
|
|
'agent_type': 'analyst',
|
|
|
|
|
'tools': ['database', 'api_calls'],
|
|
|
|
|
'visibility': 'market',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': '趋势洞察模板',
|
|
|
|
|
'description': '把多源状态汇总为趋势与判断。',
|
|
|
|
|
'instructions': '对比近期变化,输出趋势、证据、判断与建议动作。',
|
|
|
|
|
'agent_type': 'analyst',
|
|
|
|
|
'tools': ['database', 'code_execution'],
|
|
|
|
|
'visibility': 'market',
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2026-03-24 15:07:19 +08:00
|
|
|
def _is_bootstrap_enabled(settings) -> bool:
|
|
|
|
|
return bool(settings.ADMIN.strip() and settings.ADMIN_EMAIL.strip() and settings.ADMIN_PASSWORD.strip())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def ensure_admin_user(db: AsyncSession, settings) -> None:
|
|
|
|
|
if not _is_bootstrap_enabled(settings):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
result = await db.execute(
|
|
|
|
|
select(User).where(
|
|
|
|
|
or_(User.username == settings.ADMIN.strip(), User.email == settings.ADMIN_EMAIL.strip())
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
existing_user = result.scalar_one_or_none()
|
|
|
|
|
|
|
|
|
|
if existing_user:
|
|
|
|
|
if (
|
|
|
|
|
existing_user.username == settings.ADMIN.strip()
|
|
|
|
|
and existing_user.email == settings.ADMIN_EMAIL.strip()
|
|
|
|
|
and existing_user.is_superuser
|
|
|
|
|
):
|
|
|
|
|
return
|
|
|
|
|
raise RuntimeError('admin bootstrap identity conflict')
|
|
|
|
|
|
|
|
|
|
admin_user = User(
|
|
|
|
|
username=settings.ADMIN.strip(),
|
|
|
|
|
email=settings.ADMIN_EMAIL.strip(),
|
|
|
|
|
hashed_password=get_password_hash(settings.ADMIN_PASSWORD),
|
|
|
|
|
full_name=settings.ADMIN_FULL_NAME or None,
|
|
|
|
|
is_active=True,
|
|
|
|
|
is_superuser=True,
|
|
|
|
|
)
|
|
|
|
|
db.add(admin_user)
|
|
|
|
|
try:
|
|
|
|
|
await db.commit()
|
|
|
|
|
except IntegrityError:
|
|
|
|
|
await db.rollback()
|
|
|
|
|
result = await db.execute(
|
|
|
|
|
select(User).where(
|
|
|
|
|
or_(User.username == settings.ADMIN.strip(), User.email == settings.ADMIN_EMAIL.strip())
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
existing_user = result.scalar_one_or_none()
|
|
|
|
|
if (
|
|
|
|
|
existing_user
|
|
|
|
|
and existing_user.username == settings.ADMIN.strip()
|
|
|
|
|
and existing_user.email == settings.ADMIN_EMAIL.strip()
|
|
|
|
|
and existing_user.is_superuser
|
|
|
|
|
):
|
|
|
|
|
return
|
|
|
|
|
raise
|
|
|
|
|
await db.refresh(admin_user)
|
2026-03-29 20:31:13 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
async def ensure_builtin_skills(db: AsyncSession, preferred_owner_id: str | None = None) -> None:
|
|
|
|
|
owner = None
|
|
|
|
|
if preferred_owner_id:
|
|
|
|
|
owner_result = await db.execute(
|
|
|
|
|
select(User).where(User.id == preferred_owner_id, User.is_active == True)
|
|
|
|
|
)
|
|
|
|
|
owner = owner_result.scalar_one_or_none()
|
|
|
|
|
|
|
|
|
|
if not owner:
|
|
|
|
|
owner_result = await db.execute(
|
|
|
|
|
select(User).where(User.is_active == True).order_by(User.is_superuser.desc(), User.created_at.asc())
|
|
|
|
|
)
|
|
|
|
|
owner = owner_result.scalars().first()
|
|
|
|
|
|
|
|
|
|
if not owner:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
existing_result = await db.execute(select(Skill.name))
|
|
|
|
|
existing_names = set(existing_result.scalars().all())
|
|
|
|
|
|
|
|
|
|
missing_skills = [
|
|
|
|
|
Skill(
|
|
|
|
|
owner_id=owner.id,
|
|
|
|
|
name=item['name'],
|
|
|
|
|
description=item['description'],
|
|
|
|
|
instructions=item['instructions'],
|
|
|
|
|
agent_type=item['agent_type'],
|
|
|
|
|
tools=item['tools'],
|
|
|
|
|
required_context=[],
|
|
|
|
|
output_format=None,
|
|
|
|
|
visibility=item['visibility'],
|
|
|
|
|
is_builtin=True,
|
|
|
|
|
team_id=None,
|
|
|
|
|
is_active=True,
|
|
|
|
|
)
|
|
|
|
|
for item in BUILTIN_SKILLS
|
|
|
|
|
if item['name'] not in existing_names
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if not missing_skills:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
db.add_all(missing_skills)
|
|
|
|
|
await db.commit()
|